Re: [linux-audio-dev] back to the API

New Message Reply About this list Date view Thread view Subject view Author view Other groups

Subject: Re: [linux-audio-dev] back to the API
From: Paul Barton-Davis (pbd_AT_Op.Net)
Date: ma loka   18 1999 - 23:40:02 EDT


>Anyway, I think the closure should be kept as far away from the API
>as possible. As I see it, it's a *private* struct, possibly with a
>very simple public header struct.

i see it oppositely: the API has to define what gets passed to various
plugin function calls. By making it the plugin itself, the API becomes
completely amenable to source-code compatible alteration. If you
specify anything else, any time you want to change the argument type
or number, everything needs to be recoded, not just recompiled.

before I get going, some suggested terminology changes:

destination "ports" are not at all similar to source "ports".
destinations contain LISP-like linked lists of ptrs to events in
sources' "typical-C-like" linked lists.

so ... i am using the term "event source" for a "source port" - a
place where events appear from the engine's POV - and "event
destination" for places where a pointer to the event gets posted
(i.e. where they appear from a plugin/client POV).

> at the moment, i am preferring an implementation that uses fixed event
>> sizes, since events are just "differences that make a difference".
>
>How big?

     sizeof (event_timestamp_field) +
     sizeof (event_type_field) +
     sizeof (event_extra_size_field) +
     sizeof (event_target_id_field) +
     sizeof (union {
           .... various data types ...
     })

either way, i figure there should be two OOB mechanisms for events
requiring more space than fits into the union: one would use a void *
member of the union, and would point to space managed entirely by the
plugin (via some allocation/deallocation API that we provide). The
other would be via an second such member, but would point to space
allocated by the engine, and released by the engine after the event is
processed.

My current prototype-in-progress implementation looks like this:

typedef struct event {
    msc_timestamp_t ev_timestamp; /* when it happened */

#define EV_EVENT_TYPE_MASK 0x000000ff
#define EV_VALUE_TYPE_MASK 0x000fff00

#define EV_EVENT_TYPE_DELTA 0x01
#define EV_EVENT_TYPE_ABSOLUTE 0x02
#define EV_EVENT_TYPE_INCREMENT 0x03
#define EV_EVENT_TYPE_DECREMENT 0x04

#define EV_VALUE_FLOAT 0x00100
#define EV_VALUE_DOUBLE 0x00200
#define EV_VALUE_UINT32 0x00400
#define EV_VALUE_INT32 0x00800
#define EV_VALUE_UINT16 0x01000
#define EV_VALUE_INT16 0x02000
#define EV_VALUE_CHAR 0x04000
#define EV_VALUE_STRING 0x08000
#define EV_VALUE_POINTER 0x10000 /* event source allocates space */
#define EV_VALUE_DATA 0x20000 /* engine allocates space */

    unsigned int ev_type; /* type of event */
    unsigned int ev_size; /* zero if val.{data,v} not used, otherwise
                                     specifies the size of val.{data,v}
                                   */
    unsigned int ev_target_id; /* what changed */

    union {
        float f;
        double d;
        unsigned int u;
        int i;
        unsigned short us;
        short s;
        char c;
        char *str;
        void *v; /* points to non-event memory */
        unsigned char *data; /* points to non-event memory,
                                    deleted each plugin process()
                                    cycle.
                                 */
                                    
    } val; /* change value; see EVENT_TYPE_MASK
                                    to interpret.
                                 */

    /* PRIVATE */

    struct event *next;

} event_t;

if we believe that a common case is to pass around, say, new absolute
values of structures rather than basic C types, then we can have a
field in the union along the lines:

      unsigned char structure[EV_MAX_DATA_SIZE];

and use it with casts. But I'd expect EV_MAX_DATA_SIZE to be small
(say 16-32 bytes).

note that events take essentially 3 arguments:

     - what changed (ev_target_id) - this may refer (from a plugin's
               point of view) to some C parameter in memory, or it
               may not - the engine doesn't care
     - how it changed (delta from old value, absolute new value,
               increment or decrement from old value)
     - value (interpreted based on the kind of change)

increments and decrements do not take a value argument. they are a
special case that could arguably be done away with if they turn out to
be infrequently used. it just struck me that the frequent use of C's
++ and -- operators might make it worth representing such events in a
way that avoided having to do an extra assignment when setting up the
event.

>> >As soon as the global heap of buffers is turned into a freelist, we
>> >get search and splitt overhead, risk of complicated memory leak bugs,
>> >deallocation overhead, and most importantly; _memory fragmentation_.
>>
>> nope. not if the objects are all the same size.
>
>Well, I was thinking about allocation of data buffers, not the
>events themselves. Event memory management is not a problem, not even
>with dynamic size with a sane size limit.

i don't see why data buffers are such a problem. they don't get
allocated and deallocated very often - its not even clear to me why
malloc(3) is unacceptable. a plugin would establish its buffers at
init time, might occasionally allocate some afterwards, and would
destroy them when removed.
                   
> Is it safe to split too big events into multiple
>events, with respect to ordering of events with the same timestamp?
>(I think they should come in the order they're sent, but what if
>someone else is sending the same kind of events to the same place?)

can't happen. events appear at an event source. only one "object" has
access to the event source. the event can go to many event
destinations but thats different: these are just LISP-like lists of
pointers to events. the object that owns the event source posts events
to it in the order received, so that when the engine inspects all
active event sources, it finds them already sorted in time order on
any given event source. its job is to build the event cell lists for
each destination, which requires mux-ing each relevant source and
sorting by timeorder while so doing. thats all.

i should have the prototype code working tomorrow.

>> and of course, note that since only the engine allocates and
>> deallocates from both pools, no locks are necessary.
>
>How do you send events to the engine from other threads in that case?

the engine is subscribed to all event sources. before building the
event lists for each subscriber to all active event sources, it has to
make a pass through the current events to see if there is anything
earmarked for it. it does this by inspecting the ev_target_id field,
to see if any of the message concern any of its "parameters".

but either way, the quote from me above is misleading. each
event_source has its own event pool. its only the event_cell pool
(used to build event lists for destinations) that is owned by the
engine.

>real time system. And unless the lookup really finds a string most of
>the time, you might as well copy the data right away.

fair point. probably quite a reasonable pragmatic approach. there are
certainly some pathological cases where its the wrong thing to do, but
they would reflect a combination of bad plugin GUI programming and a
bad user interface.

BTW: can we *please, Please, PLEASE* avoid the pseudo-OOP code ala GTK
with "simple public header structs" etc. ? C simply cannot provide
"private" and "public" compile-time safety, so trying to provide
it only makes the code harder to read, and the macros more numerous. I
am sick of writing stuff in GTK like:
   
          gc = GTK_WIDGET(t)->style->fg_gc[GTK_WIDGET_STATE(GTK_WIDGET(t))];

all of which reflects this use of "header structs" that became so
prevalent as many people wanted the benefits of OOP without the cost.

Lets just make the structs open and clear, and mark areas that are
off-limits to plugins etc. with clear comments. It will make our life
much easier down the road, I think.

The only exception I have ever come across for this has been generic
lists, which allow manipulation of any object in a list as long as its
initial structure shares the same declarations as the "generic list
element". I haven't yet found a place in this system where that might
be useful.

--p


New Message Reply About this list Date view Thread view Subject view Author view Other groups

This archive was generated by hypermail 2b28 : pe maalis 10 2000 - 07:27:59 EST