The Prizm OS supports 10 software timers, which allow a function to be called at a certain time in the future and then periodically until its timer is stopped (or the function to call could be made to stop its timer after, for example, one call). Timers provide what is, so far, the thing closest to multi-threading available on unofficial Prizm add-in development. The OS doesn't appear to have any other multi-threading mechanisms in place.
Timers can be controlled using a few syscalls.
System and user timers
Out of the 10 timers, only six (numbers 5 through 10) are intended for use by child processes, that is, add-ins and built-in OS apps. These user timers are automatically stopped when the child process is not on the foreground: for example, whenever the Main Menu is open or the main eActivity screen is shown while a strip is kept in the background. When the process is switched back to the foreground from the Main Menu, all installed timers are started again, even if they were never started by the process. The user timers are also uninstalled automatically when the child process is switched (i.e., when an add-in or built-in app quits for another to take place).
The first four timers (numbers 1 to 4) are system timers and are stopped and started by the OS as needed, independently of which child process is running. Because of this, their handlers must be located in a memory region that is fixed in the same place, independently of the OS state, and is not cleared when switching child processes. Some system timer slots are always taken and have a hard-coded meaning to the OS, as is the case with the timers that blink the cursor or handle USB communication (timer 4). Using system timers is almost always a bad idea and can lead to a permanent brick, so if you use them make sure you know what you are doing.
The timers work by relying on a periodic NMI to occur that will cause the timer table to be processed. For each timer, if the timer is enabled and running, then the timer's counter will be decremented. If it hits 0, it will copy the frequency value to the counter, stop the timer from recursing, and invokes the callback. Because the timer keeps running, the timer's counter continues to decrease until it hits -1, meaning a timer was missed. It is not fully understood how the timer miss is handled other than a large delay in between ticks occurs.
Although timers are very useful, allowing software to give the appearance of multi-threading operation on simple tasks (like setting the status area text) even when GetKey is employed, their use has some drawbacks:
- Bfile syscalls cause unexpected behavior, when called from add-ins, if there are timers installed (apparently, they don't even need to be running) - see Incompatibility between Bfile Syscalls and Timers. If one wants to use timers in an add-in, one must stop and uninstall them before performing operations on the storage memory, and not forget to restore them later. Developing wrappers to the Bfile and Timer_* syscalls, that automatically keep track of used timers and uninstall and restore them when a Bfile operation is performed, is certainly a valid solution for this;
- Direct-drawing (DD) display functions can't be called from timer handlers. This unfortunately includes Bdisp_PutDisp_DD. This is because DD functions are not reentrant. However, modifying VRAM is mostly fine.
- Timers that take too long to execute will cause subsequent firing to be heavily delay due to the timers still running when the timer callback is called. It is recommended to limit the amount of processing that will occur in the timer's callback to prevent this from happening.
- The fact that all timers are started when switching back to the child process, independently of their previous state, requires that one uninstalls all timers that must absolutely not run in a given period, as merely stopping them means that they will run after process switching (and eventually at other times, yet to be discovered, too).
Because of this, timer usage may become a bit complex, or impossible, in certain cases. If all you need in your code is performing an operation after a certain delay, you might not need to use timers: you can develop a custom function which checks with the Real-Time Clock if the delay has passed, or you can use a syscall like RTC_Elapsed_ms (non-blocking) or OS_InnerWait_ms (blocking). Otherwise you'll need to find ways to deal with the limitations above.
Information on timers is kept on a table with 10 slots. Each timer slot is 16 bytes long, and is composed of four int variables (This information is valid for the fx-9860G and Prizm):
- slot - Indicates timer state. -1 when the slot is free, 0 when it is stopped and 1 when running;
- slot - Address of the handler, 0 when the slot is free;
- slot - Value that controls the timer handler calling frequency, -1 when the slot is free;
- slot - Counts down to zero from the value in slot; the timer handler is called when it reaches zero. Is -1 when the slot is free.