This post is a walk-through of the C code sample MinimalGazeDataStream which can be found in the EyeX SDK for C/C++.

In this walk-through, the code of MinimalGazeDataStream.c will be built up and explained incrementally, from a main function with a few lines of code to a functioning EyeX enabled console application.

The walk-through will not explain how to set up the correct project references and libraries, as this will be covered in a separate post.

Before we start: A short note on using the C API as is

The different “Minimal…” samples in the EyeX SDK for C/C++ are meant to show what you at least have to do to have an application connect to the EyeX Engine and receive some data from it via the C API.

This level of detail on how to use the C API is mainly of interest for developers that want to build a language binding or GUI specific EyeX framework on top of the C API. But, a basic knowledge of how it really works at the C API level is probably good to have for any developer using one of the language bindings and EyeX frameworks built on top of it.

Initialize EyeX and create a context handle

Now, let’s jump right into the main function of our MinimalGazeDataStream console application and add the first lines of EyeX code.

The first thing the application needs to do, to be able to start using the EyeX client library, is to initialize the library, and then create an interaction context. The context handle is the fundamental point of access to do anything in the library. You need it in order to create a snapshot and the interactors in it, register event and query handlers, and to establish a connection to the engine. The concepts of snapshot, interactor and event are further explained later in this walk-through.

In the main method of MinimalGazeDataStream.c you will find the following lines of code that initializes the system and creates an interaction context:

The txInitializeSystem function takes a number of parameters that are used for configuring the system. These are not of interest for this basic sample, so we supply parameters that will give us a default configuration of the system.

Connect to the EyeX Engine

Initially, the context’s connection to the EyeX Engine is disabled, meaning that there is no communication with the engine. The next step is therefore to enable the connection to the EyeX Engine.

Enabling a connection means allowing a connection to be established and kept alive until it is disabled, or until the context is destroyed.

Register for gaze data events

In this sample we want the EyeX Engine to stream gaze point data events to our application. We need to do two things in order for this to happen: 1) Tell the EyeX Engine what kind of data events we want it to stream, and 2) register an event handler to handle the events that the engine sends to the application.

To be able to tell the EyeX Engine what kind of data events we want, we need to know how and when to tell it.

How to tell the EyeX Engine what kind of data events to send

When an application wants to tell the EyeX Engine what kind data events it wants, it creates a so called interactor with the desired behavior, and sends that interactor to the engine in a snapshot.

In the case of an interactor for streaming data, we want a kind of interactor called a global interactor. A global interactor is called global since it is not tied to a specific region or window on the screen. Interactors that are not global have behaviors that tie them to a specific region and window on the screen.

Interactors are sent to the EyeX Engine by putting them in a snapshot and committing that snapshot to the engine. Or, more exactly, we first need to create a snapshot and then use the reference to the snapshot to create interactors in it.

To simplify the creation of a snapshot with a global interactor there is a specific API function for creating a global interactor snapshot. This function gives you a reference to the newly created global interactor that can then be fitted with the data stream behavior you want. In this case we want gaze point data events, so we add a gaze point data behavior to the interactor and set the data mode to lightly filtered.

We place the creation of the global interactor snapshot in a separate function InitializeGlobalInteractorSnapshot and call that from the main function:

We keep a reference to the created global interactor snapshot in a global variable, but we do not commit the snapshot to the EyeX Engine just yet. Why this is so is explained in the next section.

When to tell the EyeX Engine what kind of data events we want

As you have seen, the global interactor snapshot is created and prepared but not committed to the EyeX Engine during initialization in the main function. Why is that so? The simple answer is that the application and the engine has to be connected before any committed snapshots can be received and handled by the engine.

When the txEnableConnection function has been called, the client library code will try to establish a connection with the EyeX Engine. If it fails, it will try again and again. So, we cannot count on the connection being established immediately after the call.

What we need is a connection state change handler that will receive callbacks whenever the connection state changes. We add the following lines of code in the main function:

In the connection state changed handler function we check if the connection state has changed to TX_CONNECTIONSTATE_CONNECTED, and commit the global interaction snapshot if this is the case:

You might object that with this implementation the snapshot is re-committed every time the connection state changes to connected. Yes, it does, and this is intentional. If the connection is lost or disabled between the application and the engine all previously committed global interactor snapshots are thrown away and need to be re-committed in order for data to start streaming again.

Register an event handler

To be able to receive any of the events we have now specified that we want the EyeX Engine to stream, we have to register an event handler. This is done by adding the following lines to the initialization part of the main function:

The HandleEvent function will be called every time the EyeX Engine sends an event that belongs to one of the application’s interactors. To be able to differentiate between the events they all include the interactor ID of the interactor they belong to. Since the MinimalGazeDataStream application only have one interactor, there is no need to check the interactor ID. The content of the event is extracted to find the values of the latest gaze point:

Error handling

Except for the code covered so far in this walk-through, there is some code that handles different error states and the main application logic.

The error handling is kept simple in this application. Every EyeX function call returns a result code and we just check if that result code is TX_RESULT_OK. As long as the result code is OK for all the initialization functions calls, our BOOL variable ‘success’ stays true. The result of the initializaiton is printed to the console. The lines of code implementing this can be found in the middle section of the main function.

In the connection state changed handler we simply print a message and do nothing else for the non-connected states.

The last error handling code to point out, is the callback function OnSnapshotCommitted. This function is called when a snapshot has been committed, and its parameter contains the result of the commit. This function is useful during development, to catch bugs related to malformed snapshots, interactors or behaviors. It does not do anything in release builds.

Context shutdown at application shutdown

When the application has gone through the EyeX initialization calls, the main thread is halted by waiting for a key press from the user. All callbacks to the application are done on threads spawned by the EyeX client library.

When the user presses a key, the main thread continues and the application will shut down. Before shutting down, though, we want to end things gracefully with the EyeX Engine. We add calls to release any remaining references we no longer want to keep, we disable the connection to the EyeX Engine and we shut down the interaction context.

All EyeX object references should be released in the opposite order in which they were created. It is important to always release unused references to avoid memory leaks and spurious behavior. (See also the call to txReleaseObject in the InitializeGlobalInteractorSnapshot function above).

The final lines of code that we add to the main function are highlighted below:

And that ends this walk-through.

Summary

The MinimalGazeDataStream sample shows what at least has to be done for an application to connect to the EyeX Engine and to stream gaze point data from the engine to the application using the C API.

Many of the necessary steps are handled behind the scenes in the different EyeX frameworks adapted for different GUI frameworks.

EDIT Sep 8, 2014: Updated code snippets for EyeX Engine version 0.10.0

8 thoughts on “C API sample walk-through: MinimalGazeDataStream

  1. Hi,

    using the latest C API 0.31 and following the MSVC2012 tutorial I still cannot find this function txInitializeEyeX and this definition TX_EYEXCOMPONENTOVERRIDEFLAG_NONE.
    Should I initialize the eyetracker with a different function?

    Thank you,

    MWM

    • Hi Max,

      The txInitializeEyeX function is used to initialize the EyeX client library so your client application can be connected to the EyeX Engine. The function is declared in the EyeXEnv.h header file, which is included by the EyeX.h header file which should be referenced from your C/C++ project. The TX_EYEXCOMPONENTOVERRIDEFLAG_NONE is found in the EyeXClientTypes.h header, also included by EyeX.h. The header files are found in the include/eyex folder in the SDK package if you want to double check that you have the correct version of the header files. Have you set up your project according to any of the suggested ways in Walkthrough: Setting up a C/C++ project for EyeX C API?

      /Jenny

  2. Hi,

    I have unzipped the TobiiEyeXSdk-Cpp-0.24.353 and inside the includes there is no EyeXEnv.h.
    The EyeXClientTypes.h is here but no definition for the TX_EYEXCOMPONENTOVERRIDEFLAG_NONE.

    Am I using the wrong version of the API?

    Thak you,

    Max

    • Hi,

      How to store data is a generic rather than an EyeX specific programming question which I will not answer here. Please, make sure the application you are developing is in line with the Tobii EyeX SDK license agreement paragraph 3.4.1 (which in summary reads: “[eye-gaze] data may only be stored in primary memory and only while it is being processed”). Otherwise, contact Tobii to negotiate a separate license agreement for you application.

Leave a Reply