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),'orwith@EverySet%()(repeatedtimer).' 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'calledcontinuously,especiallywhilewaitingforuserinput' and during long calculations.'ThismeansthatthetimersdonotworkduringstatementslikeINPUT' or EVENT, and during GUI functions such as ALERT or LISTSELECT.'(SuchfunctionalitywouldhavetobeimplementedwithintheX11-Basic' source code itself.)'' This program works only in the interpreter (not with bytecode),'sinceitusesEVALtocallthetimerprocedures.'Following global variables are used by the "timer engine":Dim timerInterval(1), timerDue(1), timerName$(1), timerFlag%(1)'timerInterval()=timeinsecondsuntilnextactivation.'timerDue() = time of next activation.'timerName$()=nameoftheproceduretocall'timerFlag%() = 0 for inactive/empty, 1 for active (once), 2 for repeating, 3 for paused.x$ = ""signal%=0timer1% = @EverySet%(1, "@Print0()")timer2% = @AfterSet%(4, "@AllowInput()")While x$="" @TimerManager()WendPrint "You typed: "; x$timer3% = @EverySet%(0.5, "@Print1()")timer4% = @AfterSet%(4, "@IncreaseSignal()")While signal%=0 @TimerManager()Wendret% = @EveryKill%(timer3%)Print "Timer 3 killed."timer5% = @AfterSet%(3, "@IncreaseSignal()")While signal%=1 @TimerManager()Wendret% = @EveryKill%(timer1%)Print "Done."End'===========CustomTimerHandlersforthetest:===========ProcedurePrint0()Print"0 ";ReturnProcedurePrint1()Print"1 ";ReturnProcedureAllowInput()Print"Type something: ";Inputx$ReturnProcedureIncreaseSignal()incsignal%Return'============ Functions for the "timer engine": =================Procedure TimerManager() 'Callsallactivetimersifdue.'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 !'aone-timetimerevaltimerName$(i%)timerFlag%(i%)=0Case2!'a repeated timer eval timerName$(i%) timerDue(i%) = t+timerInterval(i%) EndSelect EndIf Next i% pause 0.001 !release timesliceReturnFunction AfterSet%(interval, proc$) 'Createsanewtimerandreturnsitshandle(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%EndFunctionFunction EverySet%(interval, proc$) 'Createsanewrepeatedtimerandreturnsitshandle(index).Localid%id%=@GetFirstEmptyTimer%(0)timerFlag%(id%)=2timerName$(id%)=proc$timerInterval(id%)=intervaltimerDue(id%)=Timer+intervalReturnid%EndFunctionFunctionEveryPause%(id%)'Pause the repeated timer no. id%. 'Noerrorcheckingifid%iswithinarraybounds.If(timerFlag%(id%)And2)timerFlag%(id%)=3Return0ElseReturn1!'signal error: is not a repeated timer. EndIfEndFunctionFunction EveryContinue%(id%) 'Reactivateapausedrepeatedtimer.'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 !'signalerror:isnotarepeatedtimer.EndIfEndFunctionFunctionEveryKill%(id%)'Kill a repeated timer. 'Noerrorcheckingifid%iswithinarraybounds.If(timerFlag%(id%)And2)timerFlag%(id%)=0Return0ElseReturn1!'signal error: is not a repeated timer. EndIfEndFunctionFunction GetFirstEmptyTimer%(start%) 'Returnsindexoffirstinactivetimerslot,andcreatesanewoneifthereisnone.Locali%,items%items%=Dim?(timerInterval())Fori%=start%Toitems%-1IftimerFlag%(i%)=0Returni%EndIfNexti%DimtimerInterval(items%+1),timerDue(items%+1),timerName$(items%+1),timerFlag%(items%+1)Returnitems%EndFunction
Last edit: Wanderer 2015-04-12
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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."
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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.
Last edit: Wanderer 2015-04-12
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
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:
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."
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