Every 5 seconds

New to FreeBASIC? Post your questions here.
Westbeam
Posts: 239
Joined: Dec 22, 2009 9:24
Contact:

Every 5 seconds

Post by Westbeam »

Hi
I want to show a message every 5 secounds, but how?

EDIT: I found it out:

Code: Select all

Dim As Integer laserding
Do
If buttons=1 And laserding<=Timer-1 Then
		laserding=Timer
End If
Loop
Cman
Posts: 30
Joined: May 10, 2011 22:01

Post by Cman »

You can use the sleep function.
Richard
Posts: 3096
Joined: Jan 15, 2007 20:44
Location: Australia

Post by Richard »

This is how I would do it.

Code: Select all

' align timing at a 5 second multiple to see delay in fractional part
Dim As Double next_time = 5 * Int(Timer / 5)

Do  ' this is your main loop
    ' do what you want here
    
    If Timer > next_time Then   ' test if times up
        next_time += 5  ' schedule next message in 5 seconds
        Print "Message time is"; Timer, " Next at "; next_time
    End If
    Sleep 10    ' release time to the system
Loop Until Inkey <> ""  ' press any key to exit
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Post by fxm »

If you do not have a main loop continuously sequenced, you can encapsulate for example the procedure of Richard in a thread.

Code: Select all

Dim Shared terminate As Integer = 0

Sub mythread (Byval param As Any Ptr)
' align timing at a 5 second multiple to see delay in fractional part
  Dim As Double next_time = 5 * Int(Timer / 5)
  Do  
    If Timer > next_time Then   ' test if times up
        next_time += 5  ' schedule next message in 5 seconds
        Print "Message time is"; Timer, " Next at "; next_time
    End If
    Sleep 100    ' release time to the system
  Loop Until terminate = 1
End Sub

Dim thread As Any Ptr
thread = ThreadCreate(@mythread, 0)

sleep 60000 ' wait 1 minute or press any key to exit

terminate=1
ThreadWait (thread)
TJF
Posts: 3809
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:

Post by TJF »

@fxm

Avoid global variables

Code: Select all

SUB mythread (BYVAL param AS ANY PTR)
' align timing at a 5 second multiple to see delay in fractional part
  VAR next_time = TIMER
  WHILE *CAST(INTEGER PTR, param)
    IF TIMER > next_time THEN   ' test if times up
      next_time += 5  ' schedule next message in 5 seconds
      PRINT "Message time is"; TIMER, " Next at "; next_time
    END IF
    SLEEP 100    ' release time to the system
  WEND
END SUB



' ***** main *****
VAR thread_run = 1
VAR thread = THREADCREATE(@mythread, @thread_run)

SLEEP 60000 ' wait 1 minute or press any key to exit

thread_run = 0
THREADWAIT(thread)
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Post by fxm »

@TJF

I agree with you, but remember we are in the forum 'beginners' (the use of pointer is less obvious and more risky for them)

I coded this way to stick as closely as the example of the documentation on 'ThreadCreate'.

But let's purist to the end:
- delete the comment that is no more appropriate
- output the next time value and not its pointer.

Code: Select all

SUB mythread (BYVAL param AS ANY PTR)
  VAR next_time = CAST(DOUBLE PTR, param)
  DO
    IF TIMER > *next_time THEN   ' test if times up
      *next_time += 5  ' schedule next message in 5 seconds
      PRINT "Message time is"; TIMER, " Next at "; *next_time
    END IF
    SLEEP 100    ' release time to the system
  LOOP UNTIL *next_time = 0
END SUB



' ***** main *****
VAR thread_time = TIMER
VAR thread = THREADCREATE(@mythread, @thread_time)

SLEEP 60000 ' wait 1 minute or press any key to exit

thread_time = 0
THREADWAIT(thread)
nkk_kan
Posts: 209
Joined: May 18, 2007 13:01
Location: India
Contact:

Post by nkk_kan »

Here's how i do it. just a bit different from Richard's

Code: Select all

Dim As Double msgtimer = Timer

Do
	If( (Timer - msgtimer) >= 5) Then
		Print "hello!"
		msgtimer = Timer
	End If
	
	Sleep 10
	
Loop Until InKey = Chr(27)
TJF
Posts: 3809
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:

Post by TJF »

fxm wrote:But let's purist to the end:
- delete the comment that is no more appropriate
- output the next time value and not its pointer.

Code: Select all

SUB mythread (BYVAL param AS ANY PTR)
  VAR next_time = CAST(DOUBLE PTR, param)
  DO
    IF TIMER > *next_time THEN   ' test if times up
      *next_time += 5  ' schedule next message in 5 seconds
      PRINT "Message time is"; TIMER, " Next at "; *next_time
    END IF
    SLEEP 100    ' release time to the system
  LOOP UNTIL *next_time = 0
END SUB



' ***** main *****
VAR thread_time = TIMER
VAR thread = THREADCREATE(@mythread, @thread_time)

SLEEP 60000 ' wait 1 minute or press any key to exit

thread_time = 0
THREADWAIT(thread)
This is risky!

The lines
  • *next_time += 5
and
  • thread_time = 0
may get executed at the same time, or better saying it starts with the first line, executes the second and goes back again to the first. In this case the thread won't stop.

(As a developer you can test this 1000 times and won't find any problem. But be sure, users need just a few minutes to find this bug.)


@nkk_kan:

Richard's version is less time consuming. The subtraction in your IF statement (Timer - msgtimer) need not be executed in each run of the loop.
Richard
Posts: 3096
Joined: Jan 15, 2007 20:44
Location: Australia

Post by Richard »

@ nkk_kan. Your technique accumulates all latency error. Here is a demonstration.

Code: Select all

Dim As Double msgtimer = Timer
Do
    If( (Timer - msgtimer) >= 5) Then
        Print "hello!", Timer
        msgtimer = Timer        ' slips due to response latency
    End If
    Sleep 10
Loop Until Inkey = Chr(27)
Here it is fixed so it does not accumulate, but it needs 5 seconds to be specified in two places.

Code: Select all

Dim As Double msgtimer = Timer
Do
    If( (Timer - msgtimer) >= 5) Then
        Print "hello!", Timer
        msgtimer = msgtimer + 5     ' avoids latency slip
    End If
    Sleep 10
Loop Until Inkey = Chr(27)
My approach does not slip due to latency and it only requires the delay time to be specified once. Latency slip may not always be a problem, but it is unnecessary and depending on application, is sometimes a problem. That is why I programmed it my way.
Here is a version that is unaffected by the variation in latency due to the timer clock phasing with the "Do Sleep Loop" time.

Code: Select all

' modified to reduce cyclic variation of latency 
Dim As Double next_time = 5 * Int(Timer / 5)
Do  ' this is your main loop
    ' do what you want here
    If Timer > next_time Then   ' test if time is up
        next_time += 5  ' schedule next message in 5 seconds
        Print "Message time is"; Timer, " Next at "; next_time
    End If
    If (next_time - Timer) > .012 Then Sleep 10    ' release time to the system
Loop Until Inkey <> ""  ' press any key to exit
Last edited by Richard on May 31, 2011 13:09, edited 2 times in total.
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Post by fxm »

TJF wrote:This is risky!

The lines
  • *next_time += 5
and
  • thread_time = 0
may get executed at the same time, or better saying it starts with the first line, executes the second and goes back again to the first. In this case the thread won't stop.

(As a developer you can test this 1000 times and won't find any problem. But be sure, users need just a few minutes to find this bug.)
I also thought the general case of passing a pointer as a parameter untyped (ANY) and then applying the right cast.
In our case, we must know that TIMER returns a double precision result.

It would be more secure to replace:
VAR next_time = CAST(DOUBLE PTR, param)
by
VAR next_time = CAST(TYPEOF(TIMER) PTR, param)
which is the counterpart of:
VAR thread_time = TIMER
TJF
Posts: 3809
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:

Post by TJF »

fxm wrote:
TJF wrote:This is risky!

The lines
  • *next_time += 5
and
  • thread_time = 0
may get executed at the same time, or better saying it starts with the first line, executes the second and goes back again to the first. In this case the thread won't stop.

(As a developer you can test this 1000 times and won't find any problem. But be sure, users need just a few minutes to find this bug.)
I also thought the general case of passing a pointer as a parameter untyped (ANY) and then applying the right cast.
Sorry, passing a pointer doesn't solve the problem. The line
  • *next_time += 5
will get executed in three steps
  • 1) load next_time from memory in a register
    2) increase register by 5
    3) store register to memory
When between 1) and 3) the memory value (of *next_time / threadtime or the new pointer value) gets changed outside the thread, this change has no effect (because the register won't get reloaded). This will lead to an endless loop (, because the thread won't stop and the main program waits for it).

Off course, it's a rare case but as I said: the users will find the bug! (And this kind of bug is hard to debug.)

You may use a MUTEX to prevent it (but this blows up your code alot -- my INTEGER PTR solution seems to be more elegant).
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Post by fxm »

Code: Select all

TYPE UDT_thread
  thread_time AS TYPEOF(TIMER)
  thread_terminate AS BYTE
END TYPE

SUB mythread (BYVAL param AS ANY PTR)
  VAR pt = CAST(UDT_thread PTR, param)
  DO
    IF TIMER > pt->thread_time THEN   ' test if times up
      pt->thread_time += 5  ' schedule next message in 5 seconds
      PRINT "Message time is"; TIMER, " Next at "; pt->thread_time
    END IF
    SLEEP 100    ' release time to the system
  LOOP UNTIL pt->thread_terminate = 1
END SUB



' ***** main *****
VAR thread_cmd = NEW UDT_thread(TIMER, 0)
VAR thread = THREADCREATE(@mythread, thread_cmd)

SLEEP 60000 ' wait 1 minute or press any key to exit

thread_cmd->thread_terminate = 1
THREADWAIT(thread)
DELETE(thread_cmd)
 
TJF
Posts: 3809
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:

Post by TJF »

fxm wrote:

Code: Select all

TYPE UDT_thread
  thread_time AS TYPEOF(TIMER)
  thread_terminate AS BYTE
END TYPE

SUB mythread (BYVAL param AS ANY PTR)
  VAR pt = CAST(UDT_thread PTR, param)
  DO
    IF TIMER > pt->thread_time THEN   ' test if times up
      pt->thread_time += 5  ' schedule next message in 5 seconds
      PRINT "Message time is"; TIMER, " Next at "; pt->thread_time
    END IF
    SLEEP 100    ' release time to the system
  LOOP UNTIL pt->thread_terminate = 1
END SUB



' ***** main *****
VAR thread_cmd = NEW UDT_thread
Thread_cmd->thread_terminate = 0
Thread_cmd->thread_time = TIMER
VAR thread = THREADCREATE(@mythread, Thread_cmd)

SLEEP 60000 ' wait 1 minute or press any key to exit

Thread_cmd->thread_terminate = 1
THREADWAIT(thread)
DELETE(Thread_cmd)
 
Yes, another save solution.

I removed 2 lines to shorten the code and added a WITH block for better understanding

Code: Select all

TYPE UDT_thread
  AS TYPEOF(TIMER) thread_time = TIMER
  AS BYTE thread_terminate = 0
END TYPE

SUB mythread (BYVAL param AS ANY PTR)
  WITH *CAST(UDT_thread PTR, param)
    DO
      IF TIMER > .thread_time THEN '                test if times up
        .thread_time += 5 '       schedule next message in 5 seconds
        PRINT "Message time is"; TIMER, " Next at "; .thread_time
      END IF
      SLEEP 100 '                         release time to the system
    LOOP UNTIL .thread_terminate
  END WITH
END SUB



' ***** main *****
VAR thread_cmd = NEW UDT_thread
VAR thread = THREADCREATE(@mythread, Thread_cmd)

SLEEP 60000 '                 wait 1 minute or press any key to exit

Thread_cmd->thread_terminate = 1
THREADWAIT(thread)
DELETE(thread_cmd)
I think it's a good solution for passing bigger amouts of data to the thread (by adding them to UDT_thread).

But anyway, we still need a CAST inside the thread.
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Post by fxm »

We can suppress the cast with this (the cast is implicitly done by the Sub mythread):

Code: Select all

TYPE UDT_thread
  AS TYPEOF(TIMER) thread_time
  AS BYTE thread_terminate
END TYPE

SUB mythread (BYVAL param AS UDT_thread PTR)
  WITH *param
    DO
      IF TIMER > .thread_time THEN '                test if times up
        .thread_time += 5 '       schedule next message in 5 seconds
        PRINT "Message time is"; TIMER, " Next at "; .thread_time
      END IF
      SLEEP 100 '                         release time to the system
    LOOP UNTIL .thread_terminate
  END WITH
END SUB



' ***** main *****
VAR thread_cmd = NEW UDT_thread(TIMER, 0)
VAR thread = THREADCREATE(@mythread, Thread_cmd)

SLEEP 60000 '                 wait 1 minute or press any key to exit

Thread_cmd->thread_terminate = 1
THREADWAIT(thread)
DELETE(thread_cmd)
but there is a compiler warning:
Passing different pointer types, at parameter 1 of THREADCREATE()
Last edited by fxm on May 31, 2011 18:14, edited 2 times in total.
TJF
Posts: 3809
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:

Post by TJF »

I try to avoid compiler warnings.

An UDT solution

Code: Select all

TYPE Timer_Output
  DECLARE CONSTRUCTOR(BYVAL S AS SINGLE)
  DECLARE DESTRUCTOR()
Private:
  DECLARE STATIC SUB thread_sub(BYVAL P AS ANY PTR)
  AS TYPEOF(TIMER) time_ = TIMER
  AS BYTE terminate = 0
  AS ANY PTR thread
  AS SINGLE inc
END TYPE

CONSTRUCTOR Timer_Output(BYVAL S AS SINGLE)
  inc = IIF(S > 0.5, S, .5) '        less than .5 doesn't make sense
  thread = THREADCREATE(@thread_sub, @THIS)
END CONSTRUCTOR

DESTRUCTOR Timer_Output()
  terminate = 1
  THREADWAIT(thread)
END DESTRUCTOR

SUB Timer_Output.thread_sub(BYVAL param AS ANY PTR) STATIC
  WITH *CAST(Timer_Output PTR, param)
    DO
      IF TIMER > .time_ THEN '                      test if times up
        .time_ += .inc '                       schedule next message
        PRINT "Message time is"; TIMER, " Next at "; .time_
      END IF
      SLEEP 100 '                         release time to the system
    LOOP UNTIL .terminate
  END WITH
END SUB



' ***** main *****
VAR out_thread = NEW Timer_Output(2) ' start the output, 2 sec delay

SLEEP 60000 '                 wait 1 minute or press any key to exit

DELETE(out_thread) '                                 stop the output
I'm not sure what happens if the program gets terminated without DELETE by an END statement (the DESTRUCTOR will not be called in this case).
Post Reply