Menu

Multi-timer

Programs
Wanderer
2015-04-12
2015-04-20
  • Wanderer

    Wanderer - 2015-04-12

    An example how multiple (pseudo-)asynchronous timers could be implemented in an X11-Basic program. It would be great if something like this could be part of X11-Basic itself (and so it would work also, for example, during INPUT or ALERT, and also in a bytecode file - which is not possible with this example.)

    Unfortunately I am not a C programmer, so I would not be of much help messing around with the source code of X11-Basic.

    ' MULTI-TIMER
    '=============
    ' Programming Language: X11-Basic
    '
    ' Demonstrates the use of multiple timers.
    '
    ' A timer can be set with @AfterSet%() (one-time timer),
    ' or with @EverySet%() (repeated timer).
    ' A repeated timer can be paused, continued, and killed, with
    ' @EveryPause%(), @EveryContinue%(), and @EveryKill%(), respectively.
    ' Timers are activated by the procedure @TimerManager() which must be
    ' called continuously, especially while waiting for user input
    ' and during long calculations.
    ' This means that the timers do not work during statements like INPUT
    ' or EVENT, and during GUI functions such as ALERT or LISTSELECT.
    ' (Such functionality would have to be implemented within the X11-Basic
    ' source code itself.)
    '
    ' This program works only in the interpreter (not with bytecode),
    ' since it uses EVAL to call the timer procedures.
    
    'Following global variables are used by the "timer engine":
    Dim timerInterval(1), timerDue(1), timerName$(1), timerFlag%(1)
    'timerInterval() = time in seconds until next activation.
    'timerDue() = time of next activation.
    'timerName$() = name of the procedure to call
    'timerFlag%() = 0 for inactive/empty, 1 for active (once), 2 for repeating, 3 for paused.
    
    x$ = ""
    signal%=0
    timer1% = @EverySet%(1, "@Print0()")
    timer2% = @AfterSet%(4, "@AllowInput()")
    While x$=""
      @TimerManager()
    Wend
    Print "You typed: "; x$
    timer3% = @EverySet%(0.5, "@Print1()")
    timer4% = @AfterSet%(4, "@IncreaseSignal()")
    While signal%=0
      @TimerManager()
    Wend
    ret% = @EveryKill%(timer3%)
    Print "Timer 3 killed."
    timer5% = @AfterSet%(3, "@IncreaseSignal()")
    While signal%=1
      @TimerManager()
    Wend
    ret% = @EveryKill%(timer1%)
    Print "Done."
    End
    
    '===========  Custom Timer Handlers for the test: ===========
    Procedure Print0()
      Print "0 ";
    Return
    
    Procedure Print1()
      Print "1 ";
    Return
    
    Procedure AllowInput()
      Print "Type something: ";
      Input x$
    Return
    
    Procedure IncreaseSignal()
      inc signal%
    Return
    
    '============ Functions for the "timer engine": =================
    
    Procedure TimerManager()
      'Calls all active timers if due.
      'Must be called constantly.
      Local i%, t
      t = Timer
      For i% = 0 To Dim?(timerInterval())-1
        If t>timerDue(i%)
          Select timerFlag%(i%)
          Case 1         !'a one-time timer
            eval timerName$(i%)
            timerFlag%(i%) = 0
          Case 2         !'a repeated timer
            eval timerName$(i%)
            timerDue(i%) = t+timerInterval(i%)
          EndSelect
        EndIf
      Next i%
      pause 0.001     !release timeslice
    Return
    
    Function AfterSet%(interval, proc$)
      'Creates a new timer and returns its handle (index).
      'Timers created with this function are activated once and then killed.  
      Local id%
      id% = @GetFirstEmptyTimer%(0)
      timerFlag%(id%) = 1
      timerName$(id%) = proc$
      timerInterval(id%) = interval
      timerDue(id%) = Timer+interval
      Return id%
    EndFunction
    
    Function EverySet%(interval, proc$)
      'Creates a new repeated timer and returns its handle (index).  
      Local id%
      id% = @GetFirstEmptyTimer%(0)
      timerFlag%(id%) = 2
      timerName$(id%) = proc$
      timerInterval(id%) = interval
      timerDue(id%) = Timer+interval
      Return id%
    EndFunction
    
    Function EveryPause%(id%)
      'Pause the repeated timer no. id%.
      'No errorchecking if id% is within array bounds.
      If (timerFlag%(id%) And 2)
        timerFlag%(id%)=3
        Return 0
      Else
        Return 1    !'signal error: is not a repeated timer. 
      EndIf
    EndFunction
    
    Function EveryContinue%(id%)
      'Reactivate a paused repeated timer.
      'No errorchecking if id% is within array bounds.
      If (timerFlag%(id%) And 2)
        timerFlag%(id%)=2
        timerDue(id%) = Timer+timerInterval(id%)
        Return 0
      Else
        Return 1    !'signal error: is not a repeated timer. 
      EndIf
    EndFunction
    
    Function EveryKill%(id%)
      'Kill a repeated timer.
      'No errorchecking if id% is within array bounds.
      If (timerFlag%(id%) And 2)
        timerFlag%(id%)=0
        Return 0
      Else
        Return 1    !'signal error: is not a repeated timer. 
      EndIf
    EndFunction
    
    Function GetFirstEmptyTimer%(start%)
      'Returns index of first inactive timer slot, and creates a new one if there is none.
      Local i%, items%
      items% = Dim?(timerInterval())
      For i% = start% To items%-1
        If timerFlag%(i%)=0
          Return i%
        EndIf
      Next i%
      Dim timerInterval(items%+1), timerDue(items%+1), timerName$(items%+1), timerFlag%(items%+1)
      Return items%
    EndFunction
    
     

    Last edit: Wanderer 2015-04-12
  • Markus Hoffmann

    Markus Hoffmann - 2015-04-13

    OK, but this method has a big downside: it consumes all of the processing power.
    AFTER and EVERY use the system timer (SIGALM), which is a real interrupt and therefor also work during ALERT etc. Unfortunately there is only one. One can of course set up an EVERY 0.1, something and then check with a list, what to do.

    If you want real paralell/multitasking running, I always wanted to make the SPAWN command work. But altogether this fiddles so much with the operating system, that it was too dificult to make it work under all operating systems.

    SPAWN has no effect under WINDOWS and ATARI/TOS, it works under linux and maybe Android. But I have not heavily tested it. In most cases X11-basic crashes because the code is not threadsave. Better maybe to use FORK() if you just want to start a routine which after a PAUSE plays a sound and then exits. I am not sure, that it works under WINDOWS; but there is at least a chance, that it is.
    See example program: forktest,bas

    ' test of fork command
    ' X11-Basic Version 1.08 (c) Markus Hoffmann
    '
    a=fork()
    if a=0 ! Child instance
    PRINT "Hi, I am Child !"
    PAUSE 5 ! Whenever
    ' now play the sound or something
    QUIT ! finally end this instance
    else if a=-1
    print "ERROR, fork() failed !"
    quit
    else ! parent instance
    ' go on with the normal program flow
    ' You can do as many fork()s as you like.
    PRINT "OK, lets run the program"
    '
    endif
    quit

     

    Last edit: Markus Hoffmann 2015-04-13
    • Wanderer

      Wanderer - 2015-04-20

      You write: "...it consumes all of the processing power" - On which OS? When running the program under Windows XP on a Pentium IV, the system monitor showed only 1% CPU usage. When you execute "pause 0.001" in every loop, the program should every time release a time slice to other processes. At least under Windows, this works; about other OSes I don't know. (I rarely use Linux, and in Android I do not know how to monitor CPU usage.)

      I hope to have time at some moment to test fork(), I could not yet do this.

      I had several other issues with EVERY. In Windows, it seems not to do anything at all. In Android, a simple test program like the following worked partially:

      every 1, doprint
      pause 6
      print "Done"
      End
      
      procedure doprint
        Print "1 ";
      Return
      

      It continued to print "1 "s even after END was reached. (END should perform proper cleanup, but it seems that it does not.)
      Inserting "EVERY STOP" before "END" did not help either: it said "Wrong number of parameters: EVERY."

       
  • Markus Hoffmann

    Markus Hoffmann - 2015-04-13

    What about a routine like this:
    for i=0 to 10
    @fireaction(i)
    next i
    END
    procedure fireaction(p)
    a=fork()
    if a=0
    pause p
    beep
    print "Action!";p
    quit
    endif
    return

     

Log in to post a comment.

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.