SR Research Support Site
Issues for Programming Experiments

Programming time-critical experiments (and this includes most eye-tracking experiments) requires an understanding of both the requirements for proper programming of experiments, as well as the quirks of the various versions of the operating systems. Writing applications for multi-tasking operating systems such as Windows in C environment can be confusing and full of pitfalls. Using a "simple" scripting languages, Python, or Visual Basic just hides the problems, resulting in experiments that have unpredictable timing and produce suspected or useless data - worst of all, the programmer and experimenter will not know that this is the case. In general, multi-tasking operating system programmers are not exposed to the concept of deterministic timing, and do not know how to achieve it under such operating systems. It is the goal of this section to educate the programmer in the art of deterministic experiment programming possible under Windows, and the goal of the code and libraries in this Developers Kit is to ease the development of such programs. This is a large task, but we hope this helps.

Issues for Windows Programmers

A typical Windows programmer relies on a high-level programming language such as Visual Basic, Microsoft Foundation Classes, Python, or another language that provides a "wrapper" over the complexity of Windows interface code. The problem here is that such tools are designed to make your application coexist with other programs, giving up time whenever possible to Windows and other applications readily.

This is exactly the opposite of what needs to be done to create proper experiments - we need to be greedy, keeping all the computer time to ourselves, at least during trials or dynamic display sequences. Between these, it is safe to use dialog boxes and other Windows tools. These blocks of time-critical code should be written carefully, and Visual Basic should not be used in these sections. If you insist on using Visual Basic, it should be possible to write simple libraries in C to run trials, then to call these from Visual basic, which can then be used to control the experiment, load stimuli, and so on.

This manual and the sample code show how things should be done in order to write time-critical code. Just as importantly, hard-to-write sections of the code such as display of camera images and calibration have been encapsulated in the EyeLink tracker and the eyelink_core and eyelink_core_graphics DLLs.

Windows Timing Issues

The most important issue that needs to be covered is the timing of experiments (and other programs) running under Windows. This determines what it is possible to do in an experiment, and the strategy that needs to be used to obtain the desired results.

Under a single-tasking operating system such as DOS, your experiment ran alone on the PC: except for system operations such as writing to disk, you had control over what happened and when. For example, if graphics had to be presented at precise timings, you simply created a loop and waited until the proper time to change the display had arrived. But Windows is a multitasking operating systems. This means that other programs can steal time from you at any moment. If you don't have control over the computer at the instant you should be updating the display, then the timing of you experiment will be off. If you're running a gaze-contingent window display, then the window movement may be delayed (an undesired effect). In addition, a large number of Windows functions are used by Windows as an opportunity to steal time from your program, making the execution time of these functions appear slow and unpredictable.

Fortunately, changes to the Windows kernel and availability of real-time priority levels have allowed near-realtime programming while allowing graphics and Ethernet to work - just right for EyeLink applications.

Minimizing Windows Delays

To minimize windows delays:

Even with no other programs running, the Windows kernel will try to steal some time about once a second for maintenance tasks. However, Windows allow you to place your experimental application in a real-time priority, graphics and the network continue to work while almost all other Windows tasks are disabled. This special mode is discussed next.

Windows Real-time Mode

Under Windows, it is possible place your application in a special level of real-time priority mode. This forces Windows to stop most other activity, including background disk access that might cause your experiment to have unpredictable delays. The sample experiment templates all use this mode in critical sections, including trials.

The major problem with realtime mode is that certain system functions simply cease to work. Using getkey() will probably return key presses in real-time mode, but it does this at the expense of allowing Windows to do background activity. This appears as unpredictable delays that can be as long as 20 milliseconds (but are usually on the order of 5-10 milliseconds). Therefore you should try to avoid using the Display PC keyboard for control or participant responses (keyboards are not very time accurate in any case). Instead, use eyelink_last_button_press() to detect tracker button responses, break_pressed() to detect program shutdown, eyelink_is_connected() to detect tracker disconnection, check_recording() to detect recording aborted by the eye tracker, and, if required, eyelink_read_keybutton() to monitor the EyeLink tracker keyboard.

DirectX functions works just fine under real-time mode, and EyeLink Developers Kit uses DirectX functions via SDL.

Message Pumps and Loops

An experiment needs to be deterministic: it must produce events in a sequence determined by randomization, with accurate timing. This requires that the program always has control over the computer, executing code and loops without returning control to Windows. However, if you try to run programs like this under Windows, many problems can occur: windows aren't displayed, keys can't be read, and so on.

We can make Windows work with loops by calling a message pump often. This is similar to the standard core code found in regular C programs, which calls GetMessage(), TranslateMessage() and DispatchMessage(). This serves to pass messages from the Windows kernel on to other parts of your experiment. Even Microsoft Foundation Classes (MFC) and Visual Basic application have message pumps: they are just hidden inside the run-time libraries (and are therefore harder to avoid executing).

In general, it's not a good idea to call message pumps during time-critical parts of your code. Some messages require a lot of processing by other applications, or may even redraw parts of the display. Worst of all, Windows will take whatever time it needs during calls to this function, performing disk activity and so on. In general, you don't need to call a message pump except to read keys once the full-screen experiment window has been created.

The eyelink_core library supplies several message-pump functions. The simplest way to use a message pump is to call getkey() to check if any keys have been pressed: this will also call the message pump each time. You can call a message pump explicitly with message_pump(), which also allows you to process messages for a modeless dialog box (such as the progress display window in the EDF file transfer function).

To implement long delays, you should call pump_delay() rather than msec_delay(). This will allow the operating system to process messages while waiting.

It is possible to create loops that do not contain a message pump. In this case, you must call break_pressed() in the loop to check if the program should terminate. You may also want to call escape_pressed() to see if the ESC key is pressed, which can be used to interrupt trials.

Windows Key Support

The preferred method to read keys in sections that are not time-critical is with getkey(), which also acts as a message pump and returns any keys that were recently pressed. This function returns TERMINATE_KEY if CTRL-C is pressed, or if the experiment has been terminated by pressing ALT-F4: if this code is returned, exit from any loop immediately. Some non-character keys (such as the cursor keys) are translated into the key codes required by the EyeLink tracker: these are defined in eyelink.h.

In some cases, such as during camera setup, you will want to echo the Display PC keyboard to the tracker for remote control. Use the function echo_key(), which is used like getkey() but also sends a copy of each key to the eye tracker through the link.

Terminating the Program

When a Windows program is terminated by ALT-F4, it receives WM_QUIT and WM_DESTROY system messages and its window is closed. This normally would exit the message pump in the main() function, but there is no simple message pump in our experiments. Instead, your code should detect program shutdown by one (or all) of the following: check break_pressed() for a nonzero return value, check if getkey() returns TERMINATE_KEY, or check if eyelink_is_connected() returns 0. If any of these happens, your functions must break out of any loops they are executing and return immediately. Don't use the getkey() test in time critical code, as this would cause delays. The following code is an example of pulling all of these tests into one loop:

while (1) // our loop
{
unsigned key = getkey();
if( key == TERMINATE_KEY ) break; // check for program termination
if( break_pressed() ) break; // an alternative way to check termination
if( escape_pressed() ) break; // optional test for ESC key held down
if( ! eyelink_is_connected() ) break; // exit if EyeLink tracker stopped
// YOUR LOOP CODE GOES HERE
}

It is also possible to force your program to terminate, as if ALT-F4 was pressed. By calling terminal_break(1), the break_pressed() and getkey() functions will behave as if the program was terminated. If the eyelink_core library is performing setup or drift correction, it will break out of these functions and return to your code immediately if terminal_break() with parameter 1 or exit_calibration() is called.


Copyright ©2002-2021, SR Research Ltd.