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.