API redesign progress

Jef Driesen jefdriesen at telenet.be
Mon Jun 25 13:02:01 PDT 2012


Linus,

Could you do me a favor and provide some high level overview of the api 
design that you have in mind? I'm trying to understand your point of 
view, but it's not always crystal clear to me. If you could explain your 
api design with some pseudo code, call sequences, etc I think that would 
help a lot.

Some more comments below.

On 2012-06-24 20:32, Linus Torvalds wrote:
> On Sat, Jun 23, 2012 at 1:45 PM, Jef Driesen <jefdriesen at telenet.be> 
> wrote:
>>
>> Creating the parser in the callback is convenient. In some cases you 
>> can't
>> create a parser upfront (at the time of calling dc_device_open, or 
>> even
>> immediately after it) because the actual model number is required 
>> for
>> parsing the data.
>
> Jef, this is exactly the kind of totally broken thinking that makes
> libdivecomputer interfaces so nasty to use.
>
> The user SHOULD NOT CARE about these kinds of internal 
> libdivecomputer
> issues. The whole *point* of libdivecomputer should be to hide all
> these internal implementation issues, and as long as you continue to
> think that your internal details should matter for the interface, the
> interface will be crap.
>
> Why the hell should I care that you need some model number for the
> parser? I do not even *KNOW* the model number, for chissake! The only
> thing that knows (and cares) is libdivecomputer itself.
>
> So according to your absolutely insane logic, I cannot create the
> parser at device open time, because I don't know if that particular
> device needs the model number. But that means that I should magically
> decide to create the dive parser after I get the DC_EVENT_DEVINFO
> callback. Can you not see that THAT MAKES NO SENSE! From an interface
> standpoint, that is absolutely insane. I don't even *care* about the
> DC_EVENT_DEVINFO - the fact that I actually catch it and print out 
> the
> model number on the progress bar is simply because that (to me) is an
> indicator that libdivecomputer is working at all.

Why would you want to create a parser in advance? You can't really 
parse anything until you get the first dive in the dive callback 
function. And in that case you can equally well create the parser in the 
dive callback. So I don't understand why this is a problem.

If you call dc_parser_new() from inside the dive callback, you don't 
need to care about the model number at all. If a parser needs it, it's 
guaranteed to be available in the device handle (regardless of whether 
you catch the events or not). If it's not required, then no problem 
either. That's what I meant when I said it's convenient: you don't need 
any device specific knowledge, and it will always just work.

The only real issue I see here is that the api doesn't prevent you from 
calling dc_parser_new() immediately after dc_device_open(), in which 
case you may end up with a non functional parser.

Why you use DC_EVENT_DEVINFO as an indicator is a mystery to me. We 
have DC_EVENT_{PROGRESS,WAITING} for that purpose. The primary purpose 
for DEVINFO is for use with the fingerprint feature, which subsurface 
isn't using.

> You fixed the interfaces to the top-level open code, and to the
> top-level parser create code. Please just fix this too.
>
> The fix is damn easy: get rid of the insane and useless
> "dc_parser_new()" and "dc_parser_destroy()" interfaces entirely.
> Create the parser inside of libdivecomputer (whenever it makes sense)
> and don't expose that totally idiotic and pointless interface to the
> user at all. Seriously. There is absolutely *ZERO* upside to ask me 
> to
> create a parser, since I don't care, and I cannot possibly do it at a
> sane time anyway, because for the user, no sane time exists at all.
>
> Just create the parser internally, and hide that information in the
> "dc_device_t" structure that is totally internal to libdivecomputer,
> and is just an opaque object to the users.
>
> Please. Stop these insane interfaces that only complicate the use of
> libdivecomputer, and don't help anybody.

I'm not sure what you propose here. Do you mean to integrate the parser 
object into the device object? How is that supposed to work? For example 
a device can contain more than one dive, so how do you decide which dive 
to parse? Or do you mean the dive callback should be changed to receive 
a parser object directly, pre-populated with the dive data, model 
numbers, and whatever else is necessary. Something like:

int
callback (dc_parser_t *parser, void *userdata)
{
    ...
    dc_parser_get_datetime (parser, ...);
    dc_parser_samples_foreach (parser, ...);
    ...
}

Essentially turning the dc_parser_t into a dc_dive_t object. I 
considered doing this in the early days of the libdivecomputer project, 
but there are a couple of problems with this approach.

With this api, your have no other option than to do the parsing 
directly in the callback function. However some devices (e.g. the 
sensusultra) have very strict timing requirements and you are very 
limited in the amount of processing you can do in the callback without 
messing up the downloading. In this cases it's usually better to save 
the data and process it once the download has finished, or offload it to 
a separate thread. But that's impossible because the lifetime of the 
parser is now limited to the duration of callback function.

Assume we introduce some method to extend the lifetime of the parser to 
remain valid outside the callback (e.g. a deep copy or something). We 
still can't transfer this parser to another thread because it may share 
some state with the device handle. Remember that in the roadmap emails 
[1], I announced that I'm about to introduce a new library context 
object to fix the problems related to this shared state. With the 
context object, you could create one context for the device thread and 
another one for the parser thread. If you then call:

dc_context_new (&ctx_device);
dc_context_new (&ctx_parser);

dc_device_open (&device, ctx_device, ...);

callback (...)
{
    dc_parser_new (&parser, ctx_parser, device);
}

The device and parser won't share any state and everything is fine. But 
if the parser is created directly by the device, there is no way to use 
another context object. The sensusultra timing problem can be fixed with 
internal caching, but the threading problem is a more fundamental one. 
How would you solve this?

[1] http://www.divesoftware.org/libdc/roadmap.html#17

>> You also have to take into account the use-case where you want to 
>> parse data
>> without a device connection available.
>
> No you don't. You can expose damn well any interface you want for 
> that
> case, but that's a totally separate case. That in *no* way means that
> when we have the device available, the user should somehow magically
> know when to create the parser. There is zero point.
>
>> BTW, for this situation I want to introduce some variant of the new
>> dc_parser_new function that doesn't take a device handle. I have 
>> been
>> thinking of something like this:
>>
>> dc_status_t
>> dc_parser_new_for_data (dc_parser_t **parser,
>>                        const dc_event_devinfo_t *devinfo,
>>                        const dc_event_clock_t *clock);
>
> Congratulations on making the interface EVEN MORE BROKEN.
>
> Don't do this. Seriously. Just f*cking don't. Any sane user of the
> libdivecomputer stuff will never ever care. If I opened a device
> through libdivecomputer, you already have ALL that information. It's
> totally pointless to give it to be as a callback, and then expect me
> to just give it back to you. Can't you see how STUPID that is?
>
> Make that interface what you do *internally*. Use it for the 
> pointless
> case where somebody cares. Don't force it for the normal case.

I think you misunderstood me here. This new dc_parser_new_for_data() 
function is supposed to co-exist with the dc_parser_new() function. You 
would only use it for the "offline" parsing use-case, so it's not 
intended as a replacement for the dc_parser_new() function. Isn't that 
what you meant when you wrote I can "expose damn well any interface you 
want" above?

> Even if I were to want to parse a pre-downloaded dump from a file, I
> WOULD NEVER EVER WANT TO USE THAT INTERFACE. I would want to use the
> "open dive computer" interface, and just give you the filename. And
> then libdivecomputer can again internally do the above.
>
> So feel free to do those kinds of changes for your internal flow, but
> if you really think that it's a good idea to expose that kind of
> internal detail to the user, I don't know what to say. Except to say
> that you're wrong. It's a really *bad* idea to make these kinds of
> interfaces. Don't make things worse, make them better.

I'm not convinced a file import interface is the right solution here. 
What if the application doesn't store the data in a raw file (for 
example in a database), or uses some different format (for example the 
individual dives instead of a memory dump)? If an application wants to 
support offline parsing, then it will need some specific knowledge 
anyway. For example in the Uwatec Smarttrak scenario, the data isn't 
stored in the original format, and you have to combine several database 
fields to reconstruct it. You can't do this without any knowledge of the 
format. So the internals are already exposed.

>>>  - The event handling is still horrible. There does not seem to be 
>>> any
>>> sane generic way to just get the event type and meaning.
>>
>> Which events are you referring to? The device events (e.g. 
>> DC_EVENT_*), or
>> the parser events (e.g. DC_SAMPLE_EVENT)? Can you explain in some 
>> more
>> detail?
>
> The DC_SAMPLE_EVENT interface is just crazy.
>
> Why do I have to have this kind of code in there:
>
>
>         static const char *events[] = {
>                 "none", "deco", "rbt", "ascent", "ceiling",
> "workload", "transmitter",
>                 "violation", "bookmark", "surface", "safety stop",
> "gaschange",
>                 "safety stop (voluntary)", "safety stop (mandatory)",
> "deepstop",
>                 "ceiling (safety stop)", "unknown", "divetime", 
> "maxdepth",
>                 "OLF", "PO2", "airtime", "rgbm", "heading", "tissue
> level warning"
>         };
>         const int nr_events = sizeof(events) / sizeof(const char *);
>         ...
>         type = value.event.type;
>         name = "invalid event number";
>         if (type < nr_events)
>                 name = events[type];
>
> where that set of strings is something I had to just mindlessly copy
> from your "example.c".
>
> The 'enum parser_sample_event_t' numbers are totally meaningless to
> me. They don't tell me anything. Same goes for 'enum
> parser_sample_flags_t'. And what does the even 'value' actually do?
> None of this makes any sense, and more importantly, as far as I can
> tell, I *cannot* make sense of them.
>
> Yes, the 'flags' things seems to possibly make sense as a "this 
> begins
> the event" and "this ends it". That's not documented, but at least
> it's *somehow* sensible. But if that is all it does, why is it some
> bitfield thing? What else can it possibly be? What should I do as a
> user to protect myself from you changing the flags?
>
> And the 'value' field? What does it do? It makes no sense to me, and 
> I
> can't tell what it would do. Does it have any meaning? Is it in
> degrees for the heading event? Is it minutes for the airtime event?
> What is it?

The sample events are indeed a big mess. I'm aware of this, and they 
will be removed from the api. I don't know yet how to implement an 
appropriate replacement, so at the moment the idea is to introduce a 
vendor extension type and just pass the raw binary data. Applications 
that insist on using this type of data can still get it with some 
additional effort. But at least we won't be stuck forever with a broken 
interface.

Jef


More information about the subsurface mailing list