Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C FFI Design #2

Open
jbuckmccready opened this issue Mar 3, 2021 · 0 comments
Open

C FFI Design #2

jbuckmccready opened this issue Mar 3, 2021 · 0 comments
Labels
design decision Issue involves a larger library design decision

Comments

@jbuckmccready
Copy link
Owner

jbuckmccready commented Mar 3, 2021

This is a WIP for outlining the patterns to be followed for creating the C FFI. The content in this issue will be edited and evolve into the actual top level overview documentation for the C API. I don't have a lot of experience with good C APIs, so ideas are welcome.

Error Handling

  • All functions that can error return an i32 error code
  • If the function is successful then 0 is returned (no error)
  • If the function fails in a way that is caught explicitly then a specific positive error code associated with that function is returned (e.g. maybe 1 for null input), these specific error codes are documented
  • All panic unwinds are caught, if an unwind occurred then -1 is returned, these represent an "internal algorithm error" type of bug
  • All functions return values via output parameters as pointers (return value is always reserved for error code, parameters are documented as such)
  • After an error occurs an error message string and error report string are set and stored in thread local storage
  • cavc_last_error_msg and cavc_last_error_report can be used to retrieve the last error message and last error report as char* read only null terminated c strings, due to the thread local storage it must be accessed from the same thread which called the function that had an error (char* pointer will be invalid after next error occurs)
  • The error message is a human readable error message, the error report may hold serialized data with the name of the function and the inputs given for use in reproducing the issue
  • If the error message or error report is not set then a valid null terminated empty c string is returned

Naming and Type Conventions

  • All symbols (structs and functions) start with cavc_ for namespacing.
  • f64 (double in C) is used as the numeric type.
  • u8 (unsigned char in C) is used as a logical boolean (0 is false, otherwise true).
  • Conventional null terminated char* c strings are used for strings
  • Object creation functions end with the word create, e.g. cavc_pline_create.
  • Memory free functions end with _f, e.g. cavc_pline_f.
  • Property/data assessor functions use the word get, e.g. cavc_pline_get_is_closed.
  • Property/data setter functions use th workd set, e.g. cavc_pline_set_is_closed.
  • Computed value functions use eval or find, e.g. cavc_pline_eval_area.

Option Structs

There is a need to support option parameters for more complex functions. There could be an option struct associated with each function that supports optional parameters, for example:
The cavc_pline_eval_offset could have a cavc_pline_eval_offset_o struct as one of the parameters. The cavc_pline_eval_offset_o struct could hold epsilon values and other parameters to be used by the offset algorithm and could look something like this:

pub struct cavc_pline_eval_offset_o
{
    pub pos_equal_eps: f64,
    pub slice_join_eps: f64,
    pub offset_dist_eps: f64,
    pub handle_self_intersects: u8,
}

There could then also be a cavc_pline_eval_offset_o_i function that accepts a *mut cavc_pline_eval_offset_o for initializing sensible defaults.

The issue with this is API stability if we want to add new fields to the option struct in the future. Even if all the functions take the option struct by reference/pointer the functions wont know if a newer field is present or not. And this is really bad considering everything may "seem to work" when in fact the library is reading past the end of the struct memory because the calling side is assuming a different set of fields in the struct.

One solution is to make the option struct opaque and have the construction of the struct and all fields be modified through functions but this has several downsides: a create and free function must be created for the type and memory allocation managed by the caller, all fields must have a get/set function associated with them, and from a performance perspective heap memory allocations and frees must be performed around managing the option parameters (which could be very frequent).

Another solution is to not attempt to make the option structs be forward compatible and just create a new function and new struct type if new options are added (while keeping the old function and struct the same for continued compatibility), e.g. cavc_pline_eval_offset2 and cavc_pline_eval_offset_o2.

Current plan is to follow the pattern of just creating a new function and option struct, leaving the previous version present to not break backward compatibility. However this would all be done after a 1.0 release, before then there may be some breaking changes in iteration to arrive at a 1.0 API and ideally no second version functions are required. It may also be that there is just a 2.0 release if enough changes pile up of this nature.

@jbuckmccready jbuckmccready added the design decision Issue involves a larger library design decision label Mar 3, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design decision Issue involves a larger library design decision
Projects
None yet
Development

No branches or pull requests

1 participant