I previously mentioned fixing Fleet Forth's multitasking support in the kernel. This allows PAUSE to execute while the user is typing at Forth's command line, or even just thinking about what to type. It's nice when your background tasks don't freeze while you're sitting at the keyboard thinking.
If round robin multitasking will not be used, to avoid splitting the stacks into smaller areas for each task, PAUSE can be vectored to a 'run to completion' word. There are two caveats. Any word PAUSE is vectored to must leave the stacks as they were when it began. It, and all the words it executes, must not execute PAUSE to avoid runaway recursion.
Background tasks normally execute PAUSE to switch tasks. In that case PAUSE is vectored to an actual task switcher.
The following Fleet Forth words execute PAUSE directly or indirectly through another word.
Code:
TYPE
QTYPE
KEY
(EXPECT)
INTERPRET
QUIT
DJIFFIES
JIFFIES
As well as any word which displays text, with the exception of EMIT since it displays a single character.
The error handling words ( ABORT ABORT" WHERE ) also execute PAUSE by way of TYPE but, they should not be in a background task. ABORT and ABORT" switch off any kind of multitasking by executing SINGLE , setting PAUSE to the no-op NOOP .
The following will cycle the border through all sixteen colors.
Code:
: BORDER? ( -- B )
$D020 C@ ;
: CYCLE ( -- )
BORDER? 1+ BORDER ;
' CYCLE IS PAUSE
Something like the cyclic executive Garth mentions can be set up easily:
Code:
: BACKGROUND
TASK1
TASK2
TASK3
TASK4 ;
' BACKGROUND IS PAUSE
What is missing is a way for a 'task' to run to a certain stopping point and resume where it left off. Leo Brodie's DOER/MAKE could be used. I propose a little structure of my own, inspired by DOER/MAKE, halting colon definitions. One of these runs to the halting point, where it exits. It resumes where it left off.
Code:
: H:
: ( -- ADR TRUE )
HERE 2+ ,
[ HERE 2+ >A ]
DOES> ( -- )
@ >R ;
: (HALT) ( -- )
( R: ADR -- )
R@ 2+ R> @ >BODY ! ;
: HALT
LATEST NAME> @ [ A> ] LITERAL <>
ABORT" NON HALTING WORD"
COMPILE (HALT) [COMPILE] RECURSE
; IMMEDIATE
Or if an auxiliary stack is not available, a pair of free zero page locations can be used.
Code:
: H:
: ( -- ADR TRUE )
HERE 2+ ,
[ HERE 2+ 2 ! ]
DOES> ( -- )
@ >R ;
: (HALT) ( -- )
( R: ADR -- )
R@ 2+ R> @ >BODY ! ;
: HALT
LATEST NAME> @ [ 2 @ ] LITERAL <>
ABORT" NON HALTING WORD"
COMPILE (HALT) [COMPILE] RECURSE
; IMMEDIATE
Here is a test halting word.
Code:
H: HTEST ( -- )
BEGIN
CR ." STARTING HTEST" HALT
CR ." MIDDLE OF HTEST" HALT
CR ." END OF HTEST" HALT
AGAIN ;
Each time HTEST executes, it displays one of the messages and exits.
Code:
HTEST
STARTING HTEST OK
HTEST
MIDDLE OF HTEST OK
HTEST
END OF HTEST OK
HTEST
STARTING HTEST OK
Here is an example without a BEGIN AGAIN loop.
Code:
H: TEST2 ( -- )
CR ." BEFORE HALTING." HALT
CR ." AFTER HALTING." ;
The very first time it is executed, it will display the message "BEFORE HALTING" and every time after that it will, when executed, display "AFTER HALTING" .
Here is what gets compiled for TEST2 .
Code:
' TEST2 >BODY :DIS
22937 22939
22939 7024 CR
22941 6724 (.") BEFORE HALTING.
22959 22754 (HALT)
22961 22935 TEST2
22963 7024 CR
22965 6724 (.") AFTER HALTING.
22982 2930 EXIT
47
OK
Halting colon definitions even support recursion.
Code:
H: TEST ( N -- N )
BEGIN
CR DUP SPACES ." BEGIN"
1+ DUP 4 <
IF RECURSE THEN
HALT
CR DUP SPACES ." MIDDLE"
1+ DUP 6 <
IF RECURSE THEN
HALT
CR DUP SPACES ." END"
1- DUP 0< 0=
IF RECURSE THEN
HALT
AGAIN ;
Code:
0 TEST
BEGIN
BEGIN
BEGIN
BEGIN OK
TEST
MIDDLE
MIDDLE OK
TEST
END
END
END
END
END
END
END OK
. 0 OK
If PAUSE is set to a halting colon definition, or a regular colon definition which executes more than one halting colon definition, the halting colon definition(s) will perform an action each time PAUSE is executed. This may be too frequent. Garth also mentioned alarms. An interval timer would be nice. How about the down-counter by Tim Hendtlass?
Here is DOWN-COUNTER for Fleet Forth.
Code:
: DOWN-COUNTER
2VARIABLE ( -- )
DOES> ( -- ADR )
JIFFY@ DROP
OVER 2+ @
OVER - 0 MIN
2PICK +!
OVER 2+ ! ;
That '0 MIN' is to handle the case when the jiffy timer resets to zero when it reaches twenty four hours.
A down-counter, a child word of DOWN-COUNTER , has two cells of storage. The first cell holds a value. The second cell holds the low cell of the Commodore 64 jiffy clock from the last time the down-counter was executed. Each time a down-counter is executed the amount of time, in jiffies, which passed from the last time is subtracted from the value in the first cell.
Code:
DOWN-COUNTER DELAY1 OK
300 DELAY1 ! OK
DELAY1 ? 209 OK
DELAY1 ? 118 OK
DELAY1 ? 5 OK
DELAY1 ? -445 OK
Of course, the values returned each time I type "DELAY1 ?" depends on how long I wait.
Here is sample code to cycle the Commodore 64 border and cursor colors through predetermined values without an actual multitasker other than PAUSE built into certain kernel words. It cycles the colors while I'm still able to use Fleet Forth for other things.
Code:
DOWN-COUNTER DELAY1
H: CYCLE-INK ( -- )
BEGIN
DELAY1 @ 0< 0EXIT
GREEN INK
60 DELAY1 ! HALT
DELAY1 @ 0< 0EXIT
BROWN INK
120 DELAY1 ! HALT
DELAY1 @ 0< 0EXIT
BLACK INK
180 DELAY1 ! HALT
AGAIN ;
DOWN-COUNTER DELAY2
H: CYCLE-BORDER ( -- )
BEGIN
DELAY2 @ 0< 0EXIT
GREEN BORDER
30 DELAY2 ! HALT
DELAY2 @ 0< 0EXIT
CYAN BORDER
90 DELAY2 ! HALT
DELAY2 @ 0< 0EXIT
YELLOW BORDER
60 DELAY2 ! HALT
AGAIN ;
: CYCLE ( -- )
CYCLE-INK CYCLE-BORDER ;
' CYCLE IS PAUSE
Maybe halting colon definitions, or halting words, will be useful outside the realm of simulated multitasking, I don't know. Which will be more useful, halting words or DOER/MAKE? I don't know that either.
Note: My first attempt to port halting words to Blazin' Forth did not work. Blazin' Forth's word ] doesn't just change to the compiling state, it is the colon compiler. As a result, when Blazin' Forth's ] executes, parsing of the text stream and compiling begin immediately.
The only workaround I could come up with was to incorporate the functionality of : into H: without compiling : directly.
Code:
: H: \ nothing placed on stack?
CREATE \ create a new header
SMUDGE \ hide name
CURRENT @ CONTEXT ! \ set CONTEXT equal to CURRENT
!CSP \ Blazin' Forth checks for a change in stack depth
HERE 2+ , \ allocate and initialize one cell of storage
] \ include this AFTER everything up to DOES>
DOES>
@ >R ;
The test to make sure HALT is not used in normal colon definitions was left out of the version ported to Blazin' Forth. Because of the way Blazin' Forth handles compilation, it is not easy to test if a new definition is a child of H: until after the first line of text is parsed from the keyboard or until the block it is defined in is finished.
CREATE is used in both Fleet Forth and Blazin' Forth by all the words which create a header. It is basically VARIABLE without storage allotted.
Code:
\ Fleet Forth's VARIABLE
: VARIABLE
CREATE 0 , ;
In regards to round robin multitasking, the problem with dividing the stacks so each task gets a portion is that it limits the number of tasks, as the background task's portion of the return stack has to have room for the nesting of that task and also for interrupts. One possibility is to have round robin multitasking with fewer background tasks. One or more background tasks could be a cyclic executive. The cyclic executive would need to be in a loop and include PAUSE .
Something like the following should work, but I haven't tested this yet.
Code:
\ define some halting words.
H: BG1 ... ;
H: BG2 ... ;
H: BG3 ... ;
0 $20 $13F TASK BACKGROUND
: CYCLE
BACKGROUND ACTIVATE
BEGIN
BG1 BG2 BG3 PAUSE
AGAIN ;
BACKGROUND LINK-TASK
\ define and link other tasks
...
\ switch on multitasking
MULTI