Roadmap

Jef Driesen jefdriesen at telenet.be
Fri Dec 16 21:39:36 UTC 2011


Hi,

I have a couple of additions to the previous email. As usual nothing is set in 
stone, and all feedback is welcome!

14. Enumeration of supported models

Currently there is no way to retrieve the list of supported models, and as a 
result every single developer has to hard-code such a list into their 
application. It would be much better if that list could be provided by the 
libdivecomputer library directly. An additional benefit is that you'll always 
have access to an up-to-date list.

For the actual api, I'm thinking in the direction of a function that returns a 
table, containing a mapping between models and backends:

typedef dc_model_t {
       const char *manufacturer;
       const char *name;
       device_type_t type;
       unsigned int model;
} dc_model_t;

const dc_model_t *
dc_get_supported_models (void)
{
       static const dc_model_t g_models[] = {
          {"Suunto", "Vyper", DEVICE_TYPE_SUUNTO_VYPER, 0},
          {"Suunto", "Cobra", DEVICE_TYPE_SUUNTO_VYPER, 0},
          ...
          {NULL, NULL, DEVICE_TYPE_NULL}
       };

       return models;
}

Or some variant with output parameters, rather than a sentinel element to
locate the end of the array, like this:

void
dc_get_supported_models (const dc_model_t **models, unsigned int *n)
{
       ...

       *models = g_models;
       *n = C_ARRAY_SIZE (g_models) - 1;
}

But better ideas for the enumeration api are always welcome! In particular it 
would be nice if we could turn the dc_model_t structure into an opaque data type.

One possibility would be an iterator style api, similar to this:

dc_iterator_t *iterator = dc_get_supported_models ();

while (dc_iterator_next (iterator)) {
     dc_model_t *model = dc_iterator_current (iterator);

     /* Do stuff here */
     dc_model_get_name (model);
     dc_model_get_manufacturer (model);
     ...
}

dc_iterator_free (iterator);

15. Generic dc_device_open function.

Once we have support for enumerating the list of supported devices, we can 
implement a new dc_device_open() convenience function, which would take care of 
the mapping from a particular model (as obtained from the enumeration) to the 
correct xxx_device_open() function. The main advantage would be that an 
application doesn't need any changes when a new backend is added. An application 
would simply query the list of supported devices, pass the model selected by the 
user to the new function, and the libdivecomputer library will automatically 
select the matching backend.

dc_status_t
dc_device_open (dc_device_t **device, const dc_model_t *model, const char *devname);

or passing the backend type and model number explicitly:

dc_status_t
dc_device_open (dc_device_t **device, device_type_t type, unsigned int
model, const char *devname);

The only issue remaining is the devname parameter. Currently, it's only used for 
those backends using serial communication. But it's not unlikely that backends 
using some other low-level communication channel (usb, infrared, file, etc) may 
need different arguments. At the moment that's not the case, because usb and 
infrared do perform autodetection (vid/pid for usb and device name for irda), 
but that may change in the future.

One possibility to address that would be to include some flag in the model 
descriptor, that indicates the type of low-level communication channel, with an 
associated data structure for the arguments, which is then passed to the 
dc_device_open() function:

typedef dc_params_serial_t {
       const char *devname;
} dc_params_serial_t;

typedef dc_params_usb_t {
       ...
} dc_params_usb_t;

dc_status_t
dc_device_open (..., const void *params);

This flag could then also be used by applications to show the correct UI element 
for those paramters.

16. Generic dc_parser_create function.

For the parser we can do something similar. Currently an application needs to 
connect to the devinfo and clock events, store the associated data, and pass it 
back again to the xxx_parser_create() function.

However if the device object would store the event data internally, we can 
implement a new dc_parser_create() convenience function that grabs the event 
data directly from the device handle, and automatically does the right thing.

dc_status_t
dc_parser_create (dc_parser_t **parser, dc_device_t *device);

You'll only have to be careful not to call this new function before the events 
have been emitted. In practice that means at earliest when you receive the first 
dive. But that's no difference compared to the xxx_parser_create() functions of 
course.

It may be possible to workaround this limitation, but it's not trivial due to 
thread-safety issues, and I'm not sure it's worth the effort.

17. A context object for global state

As I have written before, the logging support is problematic because it relies 
on global state, which is obviously not thread-safe. A possible solution would 
be to introduce a new context object to store the global state. This approach 
has several benefits, but it will also require changes on the application side. 
Basically you'll have to create a context object first, and then pass it down to 
all calls that create other objects (e.g. device and parser):

dc_context_t *context = NULL;
dc_device_t *device = NULL;

dc_context_create (&context);

dc_device_open (&device, context, ...);
dc_device_foreach (device, ...);
dc_device_close (device);

dc_context_free (context);

Full thread-safe logging could be obtained by requiring one context object per 
thread (e.g. similar to the device and parser objects), or making the context 
object itself thread-safe (but without having to deal with a global state now). 
The context object would also allow to implement more complex initializations, 
like reading loglevels from environment variables at runtime (just to give an 
example).

18. Timezone support

The dc_datetime_t struct doesn't have any timezone support. Good timezone 
support is certainly non-trivial, but since modern dive computers often support 
a timezone setting, I believe we won't be able to ignore it. Full timezone 
support is definitely out of scope, but some basic support using a utc offset 
field would be sufficient for our use case (e.g. similar to the BSD struct tm 
tm_gmtoff extension).

Therefore I propose to add such a field as a placeholder. Even if it won't be 
using it immediately, we won't have to break backwards compatibility when we 
actually start using it later on.

19. Support partial read/write.

In many cases, the dc_device_read() and dc_device_write() functions will split 
large read/write request into multiple packets. Currently these functions either 
succeed or fail, but there is no way to indicate a partial success. Especially 
for writes, it may be good to know how much data was written correctly before a 
failure occurred.

Therefore, I propose to add an (optional) output parameter that receives the 
amount of bytes successfully read or written. The return code would function 
exactly the same as before, and indicate the status of the entire operation. But 
in the case of a partial success you receive an error, together with the actual 
number of bytes.

20. Units

Subsurface uses an interesting approach for defining values with units. 
Basically they define a special struct that makes the units very explicit. Maybe 
we can consider this approach for libdivecomputer too?

https://github.com/torvalds/subsurface/blob/master/dive.h

Note that internally libdivecomputer will always use metric units, and that 
won't change. The only change I may consider is using degrees Kelvin for the 
temperature rather than degrees Celcius. It may be slightly less convenient (for 
both metric and imperial developers), but it has the advantage that for all 
units (depth, pressure, temperature) the values are never negative.

21. Improved naming consistency: open/create/new?

Right now there are xxx_open() functions for the device handle, xxx_create() 
functions for the parser handle and a dc_buffer_new() function, each with their 
matching close(), free() and dc_buffer_free() functions.

The main difference between open() and create(), is that the create() function 
only allocates some memory resource, while the open() function also opens a 
connection with the device. But other than that, it's only a different name.

For create() and new(), the difference is that create() returns an error code, 
while new() returns a pointer. Thus the only failure new() can return is an 
out-of-memory condition (e.g. a NULL pointer), which is of course sufficient for 
a simple memory buffer object. In theory the create() function may return other 
error codes too, although at the moment that is not the case. Validation of the 
parameters (like the model code) is postponed until parsing, and the validation 
of the dive data is done in the xxx_set_data() function.

Personally, I prefer the name "new" over "create". But since it's common 
practice a new() function returns a pointer, not a status code, that may be 
confusing? Should we also change to dc_buffer_new() function to return a status 
code, or just make an exception here? (Note that in the original roadmap there 
were already plans to make all dc_buffer_xxx() functions return a self pointer, 
rather than an integer to indicate success or failure. So adding back a status 
code for the new() function would be a little weird.)

22. Add a destructor for the dive data?

The parser_set_data() function currently doesn't copy the dive data internally, 
which means an application needs to make sure the date remains alive for the 
lifetime of the parser. The main reaons why this was done is to avoid extra 
memory copies. Typically you receive the data from the dive callback (and 
optionally copy it if you want to use it outside the callback function), parse 
it, and then destroy it again (if necessary). In this scenario an extra copy 
would be wasteful.

Maybe it would be a nice idea to pass a destructor function, that the parser can 
call when it's done with the data? Then the application doesn't have to care 
anymore about keeping it alive long enough. Or just don't care about the extra 
memory copy at all, and always make a copy?

In case we don't care about the additional memory copy, then we could also 
consider dropping the parser_set_data() function. The purpose of this function 
is to be able to re-use a single parser object for multiple dives. But if we 
already copy the dive data, which is usually much larger than the parser object 
itself, then there is little reason to not allocate a new parser for every 
single dive. In this scenario, the dive data would be passed directly to the 
xxx_parser_create() function. (Note that this would also get rid of the 
limitation in #16, because now you won't be able to construct a parser in advance.)

Jef




More information about the Devel mailing list