SCI/Specifications/SCI virtual machine/Interpreter initialization and the main execution loop
Interpreter initialization and the main execution loop
Original document by Lars Skovlund
When the interpreter initializes, it sets up a timer for 60 hertz (one that "ticks" 60 times per second). This timer does two things: it lets the so-called servers execute (most notably, the sound player and input manager) and it "feeds" the internal game clock. This 60 hz. "systick" is used all over the place. For example, it is accessible using the KGetTime kernel function. Some graphic effects depend on it, for example the "shake screen" effect. In SCI1, it is also used for timing in the palette fades. And naturally, it is used in the KWait kernel call.
Basically, the initialization proceeds as follows:
- Initialize the heap and hunk
- Parse the config file and the command line
- Load the drivers specified in the config file
- Initialize the graphics subsystem.
- Initialize the event manager
- Initialize the window manager
- Initialize the text parser (i.e. load the vocabulary files)
- Initialize the music player
- Save the machine state for restarting the game later on[1]
- Allocate the PMachine stack on the heap.
- Get a pointer to the game object
- And run, by executing the play or replay method.
The right game object is found by looking in the "dispatch table" of script 0. The dispatch table has block type 7, and is an array of words. The first entry is a pointer (script relative) to the game object, for instance SQ3. If the game was restarted, the interpreter executes the replay method, play otherwise.
After looking up the address of the method in the object block, execution is started. It can be viewed as a huge switch statement, which executes continuously. When the last ret statement (in the play or replay method) is met, the interpreter terminates.
The ExecuteCode function, which contains the mentioned switch statement, is called recursively. It lets other subroutines handle the object complexity, all the ExecuteCode function has is a pointer to the next instruction. Thus, it is easy to terminate the interpreter; just return from all running instances of ExecuteCode.
So, how does an SCI program execute? Well, the play method is defined in the Game class, and it is never overridden. It consists of a huge loop which calls Game::doit continuously, followed by a pause according to the selected animation speed. That is, the script, not the interpreter, handles animation speed. Notice how the debugger very often shows the statement sag $12 upon entering the debugger? This instruction resides in Game::play, and the break occurs here because of a KWait kernel call which is executed right before that instruction. This wait takes the most execution time, so therefore the debug break is most likely to be A game programmer would then override Game::doit and place the game specific main loop here (still, Game::doit is almost identical from game to game). Execution of the Game::play main loop stops when an event causes global variable 4 to be non-zero. The last ret instruction is met, and the interpreter terminates.
- ↑ This is quite interesting, the KRestartGame kernel call is implemented using a simple setjmp/longjmp pair.