General

General API Design, Error Handling

Nearly all C API functions in fluxEngine’s API return an int to indicate whether an error has occurred or not. A return value 0 indicates that the function call was successful, while a return value -1 indicates that the function call failed. There are some methods that return a simple number, such as fluxEngine_C_v1_ProcessingContext_num_output_sinks(). In that case a non-negative return value indicates that the function call was successful (though the return value may be 0 to indicate that the number is zero), while a value -1 still indicates an error condition.

Error Handling

All API functions that can return an error will have their final parameter be fluxEngine_C_v1_Error** error. This allows the user to specify a pointer to the opaque fluxEngine_C_v1_Error structure to give some indication about the error that occurred:

1 fluxEngine_C_v1_Error* error = NULL;
2 int rc = fluxEngine_C_v1_some_function(some parameters, &error);
3 if (rc < 0) {
4     printf("fluxEngine error: %s\n",
5            fluxEngine_C_v1_Error_get_message(error));
6     fluxEngine_C_v1_Error_free(error);
7     return;
8 }

If the user supplies a pointer to capture the error information, they must free it with fluxEngine_C_v1_Error_free(), or resources will leak. However, if an API function returns 0 it is guaranteed that the error pointer was not changed and nothing was allocated.

If the user specifies NULL for the error pointer, they can still detect that a function call failed, but they cannot determine by what reason:

1 int rc = fluxEngine_C_v1_some_function(some parameters, NULL);
2 if (rc < 0) {
3     printf("fluxEngine error (unknown)\n");
4     return;
5 }

See the documentation of fluxEngine_C_v1_Error for further details on what information may be extracted in the case of an error.

Object Return Values

There are API functions that create a pointer to an internal structure within fluxEngine. These return that pointer in their penultimate parameter. Take, for example, the fluxEngine_C_v1_init() function that initializes fluxEngine. It has the following signature:

int fluxEngine_C_v1_init(void const* license_data, size_t license_data_size, fluxEngine_C_v1_Handle** handle, fluxEngine_C_v1_Error** error)

This follows the standard error handling pattern described above. Additionally it has another output parameter handle that will be filled with the pointer to the newly allocated handle on success.

This may be called in the following manner:

 1 fluxEngine_C_v1_Error* error = NULL;
 2 fluxEngine_C_v1_Handle* handle = NULL;
 3 int rc = fluxEngine_C_v1_init(license_data, license_data_size, &handle, &error);
 4 if (rc < 0) {
 5     printf("fluxEngine error: %s\n",
 6            fluxEngine_C_v1_Error_get_message(error));
 7     fluxEngine_C_v1_Error_free(error);
 8     return;
 9 }
10 // From this point on handle contains a valid pointer
11 // At the end: free the handle
12 fluxEngine_C_v1_destroy(handle);

Future-proof Structures

Some objects in fluxEngine are passed as structures with a structure_size member. These structures are designed so that they may be extended in future versions of fluxEngine, while still retaining binary compatibility with previous versions. These structures include:

There are two types of these structures: ones that are used to pass information to fluxEngine, and ones that fluxEngine uses to return data to the user.

For the former, when the user wants to pass information to fluxEngine, the following logic must be used:

1 fluxEngine_C_v1_TYPE the_struct;
2 memset(&the_struct, 0, sizeof(the_struct));
3 the_struct.structure_size = sizeof(the_struct);
4 // Set all the fields that are passed to fluxEngine to
5 // their appropriate values:
6 the_struct.field1 = value1;
7 // Call fluxEngine
8 rc = fluxEngine_C_v1_FUNC(..., &the_struct, ...);

For the latter, when the user obtains data from fluxEngine via such a structure, the following logic must be used:

 1 fluxEngine_C_v1_TYPE the_struct;
 2 memset(&the_struct, 0, sizeof(the_struct));
 3 the_struct.structure_size = sizeof(the_struct);
 4 // Some structs have some fields that indicate that
 5 // additional information is requested, set those
 6 // if desired:
 7 the_struct.xxx_requested = true;
 8 // Call fluxEngine
 9 rc = fluxEngine_C_v1_FUNC(..., &the_struct, ...);
10 // Use the fields in the_struct
11 do_something(the_struct.field1);
12 // Potentially free fields according to the
13 // documentation of the struct, e.g. via:
14 fluxEngine_C_v1_TYPE_free(the_struct.field2);

Initializing the Library

To initialize the library a license file is required. The user must read that license file into memory and supply fluxEngine with it. See read_file for example code for reading the license.

The following code demonstrates how to properly initialize fluxLicense and how to tear it down again.

 1 fluxEngine_C_v1_Error* error = NULL;
 2 fluxEngine_C_v1_Handle* handle = NULL;
 3 int rc = 0;
 4 void* license_data = NULL;
 5 size_t license_data_size = 0;
 6
 7 /* Read the entire license data from disk
 8  * (see the helper code in the documentation for a possible
 9  * implementation of this method)
10  */
11 license_data = read_file("fluxEngine.lic", &license_data_size);
12 if (!license_data) {
13     fprintf(stderr, "Could not read license file: %s\n", strerror(errno));
14     return;
15 }
16
17 rc = fluxEngine_C_v1_init(license_data, license_data_size,
18                            &handle, &error);
19 /* Once fluxLicense has been initialized, we do not need
20  * the license data anymore. */
21 free(license_data);
22 if (rc != 0) {
23     fprintf(stderr, "Could not initialize fluxEngine: %s\n",
24             fluxEngine_C_v1_Error_get_message(error));
25     fluxEngine_C_v1_Error_free(error);
26     return;
27 }
28
29 /*
30  ******************************************************************
31  * Do something with fluxLicense here.
32  ******************************************************************
33  */
34
35 /* At the end: free resources */
36 fluxEngine_C_v1_destroy(handle);

Setting up processing threads

fluxEngine supports parallel processing, but it has to be set up at the very beginning. The eastiest method to do so is via the fluxEngine_C_v1_create_processing_threads() function.

The following example code demonstrates how to perform processing with 4 threads:

1 fluxEngine_C_v1_Error* error = NULL;
2 int rc = 0;
3 rc = fluxEngine_C_v1_create_processing_threads(handle, 4, &error);
4 if (rc != 0) {
5     fprintf(stderr, "Could not setup fluxEngine processing threads: %s\n",
6             fluxEngine_C_v1_Error_get_message(error));
7     fluxEngine_C_v1_Error_free(error);
8     return;
9 }

Note

This will only create 3 (not 4!) background threads that will help with data processing. The thread that calls fluxEngine_C_v1_ProcessingContext_process_next() will be considered the first thread (with index 0) that participates in parallel processing.

Note

Modern processors support Hyperthreading (Intel) or SMT (AMD) to provide more logical cores that are phyiscally available. It is generally not recommended to use more threads than are phyiscally available, as workloads such as fluxEngine will typically slow down when using more cores in a system than are physically available.

Note

When running fluxEngine with very small amounts of data, in the extreme case with cubes that have only one pixel, parallelization will not improve performance. In cases where cubes consisting of only one pixel are processed, it is recommended to not parallelize at all and skip this step.

Note

Only one fluxEngine operation may be performed per handle at the same time; executing multiple processing contexts from different threads will cause them to be run sequentially.

Since it is currently possible to only create a single handle for fluxEngine, this means only one operation can be active at the same time; though the limitation of only a single handle will be lifted in a later version of fluxEngine.