One of the major new features in Z88 CamelForth v3.00 is support for multi-programming (although this is hardly a new feature for Forth - the very first Forths over 30 years ago had it!). The model used is closely based on that used by Pygmy Forth.
CamelForth starts up in single-tasking mode; in this mode a single task (the terminal task) is running, and the task-switching deferred word PAUSE is set to NOOP so that no time is wasted "switching" to the next task.
Multi-tasking mode must not be enabled before the first task is defined, although it can be enabled even if only the terminal task is active (in this case PAUSE will just switch out the terminal task and immediately switch it back in, since the task list is circular). The reason that you should not enable multi-tasking before defining a task is that the first time this is done, space is allocated in RAM for switching out the data stack of the terminal task; before this such space is not required. Such switching is unfortunately necessary as OZ requires that SP always be located below $2000.
In multi-tasking mode, PAUSE causes the currently active task to switch control to the next "awake" task in the circular list. You can either use PAUSE specifically, or rely on one of the following words, which invoke it:
Note that the page wait function PW does not do a PAUSE, since it is performed by an OZ call. However, a multi-programming aware version based on EKEY can be found in the loadable file pw.fth; if you use this, note that it uses window 6, and will not restore any topic names after completing.
Only the terminal task should accept input from the keyboard or load other sources. All data areas are shared between tasks (such as PAD, block buffers etc) so if you need to use these areas in more than one task, ensure that you save/restore them as required before and after each possible task switch. Each task has its own stacks, BASE and graphics settings; everything else is shared.
One final thing to be aware of is that in multi-tasking mode, EKEY (and its derivatives) use the OZ call OS_TIN to wait only a short time for a keystroke. Using this call means that the Z88 cannot time out and switch itself off as it normally does. Therefore you should make the user aware of this, or switch to single-tasking mode to allow timeouts to occur.
The first thing you need to do is define your tasks with TASK:. Each task requires approximately 400 bytes of RAM to hold its stacks and user variables. Since tasks can be set to run any word at any time, you only need to define as many as you will be running at once (excluding the terminal task, of course). For example, define a task called A:
TASK: A
Executing A returns a unique task ID, which is an argument or result for most of the remaining multiprogramming words.
Next set the task to run a particular word with TASK!. Normally this will contain an infinite loop with PAUSE at some strategic position. As an example:
: TEST BEGIN 7 EMIT PAUSE AGAIN ; ' TEST A TASK!
Finally, you can enable multi-tasking with MULTI and set the task running by WAKEing it:
MULTI A WAKE
You can disable multi-tasking at any point with SINGLE, which has the effect of locking the system into the currently running task. All tasks previously "awake" are still awake, and will start running again as soon as multi-tasking is re-enabled with MULTI.
Any task (except the terminal task) can be stopped with SLEEP. For example, to halt task A, do:
A SLEEP
It's possible to re-WAKE the task, or use TASK! first to set it to run a new word. It's perfectly safe to use WAKE on an already awake task, or SLEEP on an inactive task; however if you attempt to use TASK! on an active task, a crash could occur.
If you wish a task only to run for a specified time, include the word STOP somewhere in the word it runs; this will send the current current task to sleep and switch to the next.
Finally, you may use PURGE to deactivate all running tasks.
The following words provide useful information on task activity:
When you use TASK! to set a task to run a particular word, the task is actually set to execute the deferred word RUN. The default word for this is (RUN) which is defined in the following way:
: (RUN) ( xt -- ) CATCH DROP STOP ;
The purpose of this is to ensure that any exceptions generated by the task are silently caught and the task terminated. This is usually fine, but you may want to replace this if, for example, you want exceptions generated by your tasks to be passed to the main terminal task (in a variable for example).
Its possible to make switching between single- and multi-tasking modes automatic (this is useful because the Z88 only times out properly in single- tasking mode). This is achievable because #TASKS returns the number of active tasks (the minimum is 1 since the terminal task is always active), and STOP always switches to the next task, meaning that it's possible to use SINGLE STOP.
For automatic mode-switching, always use this code to start tasks:
task MULTI WAKE
and always use either of these phrases to stop tasks:
#TASKS @ 3 < IF SINGLE THEN task SLEEP #TASKS @ 3 < IF SINGLE THEN STOP
A demo of the multiprogramming facilities is included with Z88 CamelForth and can be loaded with S" multidemo.fth" INCLUDED. This divides the screen into 4 windows, with tasks named A and B running in two text windows and a task C running in the graphics window. Forth itself is available in another window. You can experiment by stopping and waking the various tasks, assigning them different execution tokens and so on. The sources also show the necessity of saving and restoring state information (current output window in this case) when switching tasks, with tasks A and B restoring the previous state before executing PAUSE.