UDT_timer_thread (public methods and private thread-sub)

Post your FreeBASIC tips and tricks here. Please don’t post your code without including an explanation.
fxm
Posts: 9934
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

UDT_timer_thread (public methods and private thread-sub)

Postby fxm » Dec 22, 2011 15:43

From a post of TJF:
http://www.freebasic.net/forum/viewtopi ... 058#168058
I coded an User Defined Type 'UDT_timer_thread' which allows to easily program independent timers, in order to asynchronously activate user Sub procedures with specified time-out values.


UDT_timer_thread overview:

Only 4 member procedures in public access (the first 3 returning '1' if success, '0' else):
- Function 'Set' to parametrize the considered timer (time-out in ms, pointer to user Sub procedure)
- Function 'Start' to start the considered timer
- Function 'Stop' to stop the considered timer (then, the considered timer may be re-Set and re-Start)
- Property 'Counter' to get the occurrence number of the timer
Plus an 'Any Ptr' in public access:
- Pointer field 'userdata' to point to any user data structure (optional usage)

Remark:
- Pointer to the considered timer instance is provided to the user Sub procedure in order to be able to factorize the treatment per timers group, and to address the right user data structure if used (see example for usage).

In private access:
- 4 internal variables (time-out value, pointer to user Sub procedure, handle to Sub thread, counter of occurence)
- Static Sub thread


UDT_timer_thread.bi:

Code: Select all

'UDT_timer_thread.bi

Type UDT_timer_thread
    Public:
        Declare Function Set (Byval time_out As Uinteger, _ ' time-out value in milliseconds
                              Byval timer_procedure As Sub(Byval instance As UDT_timer_thread Ptr)) _ ' pointer to the user timer Sub procedure to be called at each time-out
                              As Integer
        Declare Function Start () As Integer
        Declare Function Stop () As Integer
        Declare Property Counter () As Uinteger ' occurrence number
        Dim userdata As Any Ptr ' pointer to any user data structure
    Private:
        Dim tempo As Uinteger
        Dim routine As Sub(Byval instance As UDT_timer_thread Ptr)
        Dim handle As Any Ptr
        Dim count As Uinteger ' occurrence counter
        Declare Static Sub Thread (Byval param As Any Ptr)
End Type

Function UDT_timer_thread.Set (Byval time_out As Uinteger, _
                              Byval timer_procedure As Sub(Byval instance As UDT_timer_thread Ptr)) _
                              As Integer
    If timer_procedure > 0 And This.handle = 0 Then
        This.tempo = time_out
        This.routine = timer_procedure
        This.count = 0
        Function = 1
    Else
        Function = 0
    End If
End Function

Function UDT_timer_thread.Start () As Integer
    If This.handle = 0 And This.routine > 0 Then
        This.handle = Threadcreate(@UDT_timer_thread.Thread, @This)
        Function = 1
    Else
        Function = 0
    End If
End function

Function UDT_timer_thread.Stop () As Integer
    If This.handle > 0 Then
        Dim p As Any Ptr = 0
        Swap p, This.handle
        Threadwait(p)
        Function = 1
    Else
        Function = 0
    End If
End function

Property UDT_timer_thread.Counter () As Uinteger
    Return This.count
End Property

Static Sub UDT_timer_thread.Thread (Byval param As Any Ptr)
    Dim p As UDT_timer_thread Ptr = param
    While p->handle > 0
        Sleep p->tempo, 1
        p->count += 1
        p->routine(p)
    Wend
End Sub

timers_test.bas:

Code: Select all

'timers_test.bas

#Include "UDT_timer_thread.bi"

Dim timer1 As UDT_timer_thread
    timer1.userdata = New String("        callback from timer #1") ' simple data user structure, just to illustrate the principle
Dim timer2 As UDT_timer_thread
    timer2.userdata = New String("        callback from timer #2") ' simple data user structure, just to illustrate the principle

Sub Test_timers (Byval instance As UDT_timer_thread Ptr)
    Dim As String Ptr ps = instance->userdata
    Print *ps & ", occurrence: " & instance->Counter
End Sub

Print "Beginning of test"
If timer1.Set(1500, @Test_timers) Then
    Print "    timer #1 set OK"
    If timer1.Start Then
        Print "        timer #1 start OK"
    End If
End If
If timer2.Set(600, @Test_timers) Then
    Print "    timer #2 set OK"
    If timer2.Start Then
        Print "        timer #2 start OK"
    End If
End If
Print "    Then, any key to stop the timers"

Sleep

If timer1.stop Then
    Print "    timer #1 stop OK"
End If
If timer2.stop Then
    Print "    timer #2 stop OK"
End If
Print "End of test"
Delete Cast(String Ptr, timer1.userdata)
Delete Cast(String Ptr, timer2.userdata)

Sleep

Code: Select all

Beginning of test
    timer #1 set OK
        timer #1 start OK
    timer #2 set OK
        timer #2 start OK
    Then, any key to stop the timers
        callback from timer #2, occurrence: 1
        callback from timer #2, occurrence: 2
        callback from timer #1, occurrence: 1
        callback from timer #2, occurrence: 3
        callback from timer #2, occurrence: 4
        callback from timer #1, occurrence: 2
        callback from timer #2, occurrence: 5
        callback from timer #2, occurrence: 6
        callback from timer #2, occurrence: 7
        callback from timer #1, occurrence: 3
        callback from timer #2, occurrence: 8
        callback from timer #2, occurrence: 9
        callback from timer #1, occurrence: 4
        callback from timer #2, occurrence: 10
        callback from timer #2, occurrence: 11
        callback from timer #2, occurrence: 12
        callback from timer #1, occurrence: 5
    timer #1 stop OK
        callback from timer #2, occurrence: 13
    timer #2 stop OK
End of test


[Edit]
- 1st update after the below remark of TJF and my following response.
Last edited by fxm on Sep 17, 2019 7:40, edited 12 times in total.
TJF
Posts: 3600
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:

Postby TJF » Dec 22, 2011 19:52

Well done!

IMO you should add a PTR to pass some user data to the thread routine. Ie begin UDT_timer_thread.bi like

Code: Select all

'UDT_timer_thread.bi

Type UDT_timer_thread
  Public:
    DECLARE FUNCTION Set _
      (BYVAL time_out AS UINTEGER, _ ' time-out value in milliseconds
       BYVAL timer_procedure AS SUB(BYVAL instance AS UDT_timer_thread PTR), _ ' pointer to Sub to be called at each time-out
       BYVAL user_data AS ANY PTR = 0) _ ' pointer to user data for the thread routine
       AS INTEGER
    Declare Function Start () As Integer
    Declare Function Stop () As Integer

    AS ANY PTR userdata

  Private:
    Dim tempo As Uinteger
    Dim routine As Sub(Byval instance As UDT_timer_thread Ptr)
    Dim handle As Any Ptr
    Declare Static Sub Thread (Byval param As Any Ptr)
End Type

FUNCTION UDT_timer_thread.Set _
  (BYVAL time_out AS UINTEGER, _
   BYVAL timer_procedure AS SUB(BYVAL instance AS UDT_timer_thread PTR), _
   BYVAL user_data AS ANY PTR = 0) _
   AS INTEGER

  IF timer_procedure = 0 THEN RETURN 0
  IF This.handle THEN RETURN 0

  This.tempo = time_out
  This.routine = timer_procedure
  This.userdata = user_data

  RETURN 1

END FUNCTION

' ...
fxm
Posts: 9934
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Postby fxm » Dec 22, 2011 22:12

TJF wrote:IMO you should add a PTR to pass some user data to the thread routine.

Yes, it is a good idea to be able to pass some user data to the user timer routine, using a 'userdata As Any Ptr' pointing to any user structure.
But in order to be accessible from the user timer routine, this member userdata pointer must be defined in the Public section of the UDT_timer_thread.

Consequently, it is not necessary (IMHO) to add a new parameter to the method 'Set', because the user can directly initialize it (member userdata pointer in the Public section of the UDT). That should be even more complex for user to define a new pointer parameter to pass to the method 'Set', when the member userdata pointer already exists and is fully accessible in read/write by the user.

I propose to just add a new data-field in the Public section of 'UDT_timer_thread':
Dim userdata As Any Ptr ' pointer to any user data structure

I will update my proposed program (in my first post), UDT + example, in order to demonstrate the full capability.
fxm
Posts: 9934
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: UDT_timer_thread (public methods and private thread-sub)

Postby fxm » Sep 14, 2019 20:39

fxm wrote:UDT_timer_thread overview:

Only 4 member procedures in public access (the first 3 returning '1' if success, '0' else):
- Function 'Set' to parametrize the considered timer (time-out in ms, pointer to user Sub procedure)
- Function 'Start' to start the considered timer
- Function 'Stop' to stop the considered timer (then, the considered timer may be re-Set and re-Start)
- Property 'Counter' to get the occurrence number of the timer
Plus an 'Any Ptr' in public access:
- Pointer field 'userdata' to point to any user data structure (optional usage)

Remark:
- Pointer to the considered timer instance is provided to the user Sub procedure in order to be able to factorize the treatment per timers group, and to address the right user data structure if used (see example for usage).

In private access:
- 4 internal variables (time-out value, pointer to user Sub procedure, handle to Sub thread, counter of occurence)
- Static Sub thread

This code in the first post can be now improved, notably by the following:
- In the former version, a user procedure is triggered by a procedure callback from the timer loop. Therefore the execution time of the user procedure is included in the timer loop. So, the time-out requested by the user (and implemented with the Sleep keyword in the timer loop) may be inconstantly biased (because increased depending on the user procedure duration).
- In the new version, now a user thread is triggered by a detached-thread callback from the timer loop. So, the requested time-out is only biased by the execution time of ThreadCreate + ThreadDetach (small time about constant).

New UDT_timer_thread.bi:

Code: Select all

'UDT_timer_thread.bi

#include "fbthread.bi"

Type UDT_timer_thread
    Public:
        Declare Function Set (Byval time_out As Uinteger, _
                              Byval timer_procedure As Sub(Byval param As Any Ptr)) _
                              As Boolean
        Declare Function Start () As Boolean
        Declare Function Stop () As Boolean
        Declare Property Counter () As Uinteger
        Dim As Any Ptr userdata
    Private:
        Dim As Uinteger tempo
        Dim As Sub(Byval param As Any Ptr) routine
        Dim As Any Ptr handle
        Dim As Uinteger count
        Declare Static Sub thread (Byval param As Any Ptr)
End Type

Function UDT_timer_thread.Set (Byval time_out As Uinteger, _
                              Byval timer_procedure As Sub(Byval param As Any Ptr)) _
                              As Boolean
    If timer_procedure > 0 And This.handle = 0 Then
        This.tempo = time_out
        This.routine = timer_procedure
        This.count = 0
        Function = True
    Else
        Function = False
    End If
End Function

Function UDT_timer_thread.Start () As Boolean
    If This.handle = 0 And This.routine > 0 Then
        This.handle = Threadcreate(@UDT_timer_thread.thread, @This)
        Function = True
    Else
        Function = False
    End If
End function

Function UDT_timer_thread.Stop () As Boolean
    If This.handle > 0 Then
        Dim p As Any Ptr = 0
        Swap p, This.handle
        Threadwait(p)
        Function = True
    Else
        Function = False
    End If
End function

Property UDT_timer_thread.Counter () As Uinteger
    Return This.count
End Property

Static Sub UDT_timer_thread.thread (Byval param As Any Ptr)
    Dim As UDT_timer_thread Ptr pu = param
    While pu->handle > 0
        Sleep pu->tempo, 1
        pu->count += 1
        If pu->routine > 0 Then
            Dim As Any Ptr p = Threadcreate(Cast(Any Ptr, pu->routine), param)
            Threaddetach(p)
        End If
    Wend
End Sub

New timers_test.bas:

Code: Select all

'timers_test.bas

#Include "UDT_timer_thread.bi"

Dim As Uinteger tempo1 = 950
Dim As Uinteger tempo2 = 380
Dim As UDT_timer_thread timer1
    timer1.userdata = New String("        callback from timer #1 (" & tempo1 & "ms)")
Dim As UDT_timer_thread timer2
    timer2.userdata = New String("        callback from timer #2 (" & tempo2 & "ms)")

Sub User_thread (Byval param As Any Ptr)
    Dim As UDT_timer_thread Ptr pu = param
    Dim As String Ptr ps = pu->userdata
    Print *ps & ", occurrence: " & pu->Counter
End Sub

Print "Beginning of test"
If timer1.Set(tempo1, @User_thread) Then
    Print "    timer #1 set OK"
    If timer1.Start Then
        Print "        timer #1 start OK"
    End If
End If
If timer2.Set(tempo2, @User_thread) Then
    Print "    timer #2 set OK"
    If timer2.Start Then
        Print "        timer #2 start OK"
    End If
End If
Print "    Then, any key to stop the timers"

Sleep

If timer1.stop Then
    Print "    timer #1 stop OK"
End If
If timer2.stop Then
    Print "    timer #2 stop OK"
End If
Sleep 500, 1
Print "End of test"
Delete Cast(String Ptr, timer1.userdata)
Delete Cast(String Ptr, timer2.userdata)

Sleep

Code: Select all

Beginning of test
    timer #1 set OK
        timer #1 start OK
    timer #2 set OK
        timer #2 start OK
    Then, any key to stop the timers
        callback from timer #2 (380ms), occurrence: 1
        callback from timer #2 (380ms), occurrence: 2
        callback from timer #1 (950ms), occurrence: 1
        callback from timer #2 (380ms), occurrence: 3
        callback from timer #2 (380ms), occurrence: 4
        callback from timer #1 (950ms), occurrence: 2
        callback from timer #2 (380ms), occurrence: 5
        callback from timer #2 (380ms), occurrence: 6
        callback from timer #2 (380ms), occurrence: 7
        callback from timer #1 (950ms), occurrence: 3
        callback from timer #2 (380ms), occurrence: 8
        callback from timer #2 (380ms), occurrence: 9
        callback from timer #1 (950ms), occurrence: 4
        callback from timer #2 (380ms), occurrence: 10
        callback from timer #2 (380ms), occurrence: 11
        callback from timer #2 (380ms), occurrence: 12
    timer #1 stop OK
        callback from timer #1 (950ms), occurrence: 5
    timer #2 stop OK
        callback from timer #2 (380ms), occurrence: 13
End of test
h4tt3n
Posts: 694
Joined: Oct 22, 2005 21:12
Location: Denmark

Re: UDT_timer_thread (public methods and private thread-sub)

Postby h4tt3n » Sep 25, 2019 14:51

Nice, thanks!

Return to “Tips and Tricks”

Who is online

Users browsing this forum: No registered users and 1 guest