The Sierra Creative Interpreter | ||
---|---|---|
Prev | Chapter 7. FreeSCI |
Kernel functions are the bridge between the abstract virtual machine, and the world of real programs. The VM may be able to solve RPN equations in the blink of an eye, but what good is this if it can't read input or produce output?[1]
All of the kernel functions are stored in src/core/kernel.c. Since kernel function mapping is done during runtime by string comparison, each kernel function and its name have to be registered in the array kfunct_mappers[]. Note that each version of the SCI interpreter (at least each pre-1.000.000 version) comes with one unidentified kernel function, which is handled by k_Unknown.
Each kernel function is declared like this:
void kFooBar(state_t *s, int funct_nr, int argc, heap_ptr argp); |
state_t *s: A pointer to the state you are operating on. |
int funct_nr: The number of this function. Mostly irrelevant. |
int argc: The number of arguments. |
heap_ptr argp: Heap pointer to the first argument. |
Some kernel functions don't even need to refer to the heap. However, most of them are passed at least one, if not more parameters. This may sound shocking to you, but there is an easy way to work around the neccessity of peeling them off the heap manually: Use the PARAM macros. They are used as follows:
PARAM(x): Returns the value of the parameter x. Does not check for validity. |
UPARAM(x): Same as PARAM(x), but casts the parameter to be unsigned. |
PARAM_OR_ALT(x, y): Checks if PARAM(x) is valid and returns it, or returns y if PARAM(x) is invalid. |
UPARAM_OR_ALT(x, y): PARAM_OR_ALT(x, y) unsigned. |
Accessing the heap for both reading and writing is surprisingly important for the kernel, especially when it has to deal with functions that would usually belong into user space, like handling of doubly-linked lists. To ease this, three macros are available:
GET_HEAP(x) - reads a signed SCI word (gint16) from heap address x |
UGET_HEAP(x) - reads an unsigned SCI word (guint16) |
PUT_HEAP(x, foo) - writes the value foo to the specified heap address |
Some kernel functions, especially graphical kernel functions, additionally require the usage of what Sierra referred to as "hunk space". This is dynamically allocated memory; it can even be allocated and unallocated manually from SCI scripts by using the Load() and UnLoad() system calls (this is the sci_memory resource). To allow usage of this kind of memory, three functions have been provided:
int kalloc(state_t *, space) - allocate space bytes and return a handle |
byte *kmem(state_t *, handle) - resolve a handle and return the memory address it points to |
int kfree(state_t *, handle) - unallocate memory associated with a handle. Returns 0 on success, 1 otherwise |
Error handling and debugging probably are the most important aspects of program writing. FreeSCI provides three macros for printing debug output:
SCIkwarn(text, ...) - Print a warning message |
SCIkdebug(text, ...) - Print a debug message |
CHECK_THIS_KERNEL_FUNCTION - print the function name and parameters |
Sometimes it may happen that something goes wrong inside the kernel; e.g. a kernel function runs out of memory handles, or an internal variable somehow was set to an invalid value. In this case, kernel_oops(state_t *, char *) should be used. It prints an error message and halts the VM, which none of the macros does.
Selectors are very important for some of the kernel functions. BaseSetter(), Animate(), Display(), GetEvent() and others take data from or write directly to selectors of a specified object (passed as a parameter or retreived from a node list), or even call object methods from kernel space[2] To prepare the usage of selectors, a variable has do be declared (in src/include/vm.h, selector_map_t). This variable will carry the numeric selector ID during run time. Now, the selector has to be mapped- this is happens once during initialization, to save time. It is performed by script_map_selectors(), which is located at the end of src/core/script.c (just use the "FIND_SELECTOR" macro).
If everything went right, accessing selectors should be really easy now. Just use the GET_SELECTOR(obj, selector) and PUT_SELECTOR(obj, selector, value) macros, where obj is a heap_ptr pointing to the object you want to read from or write to, and selector is the name of the selector to use.
Example 7-1. An example for PUT_SELECTOR and GET_SELECTOR
void kSwapXY(state_t *s, int funct_nr, int argc, heap_ptr argp) { int posx, posy; heap_ptr obj = PARAM(0); posx = GET_SELECTOR(obj, x); posy = GET_SELECTOR(obj, y); /* x and y are defined in selector_map_t */ PUT_SELECTOR(obj, y, posx); PUT_SELECTOR(obj, x, posy); } |
Also, it may be neccessary to invoke an actual method. To do this, a varargs macro has been provided: INVOKE_SELECTOR(obj, selector, argc...). In theory, this macro can be used to set and read selectors as well (it would even handle multiple sends correctly), but this is discouraged for the sake of clarity.
INVOKE_SELECTOR works very much like the other macros; it must be called directly from a kernel function (or from any function supplying valid argc, argp and s).
[1] | It could be used to produce benchmarks. |
[2] | Yes, this is evil. Don't do this at home, kids! |