Post by salsamanAnyway, I can assure you that I will not consider any proposal 1.0 until
we are all (at least you, Jaromil, Niels and myself) happy with
right. we miss Tom here, who also contributed to the development and was
an important guidance for me since the beginning.
he researched on his own track and tested it, then stepped back from it,
recognizing that it would have been too hard to cope on that.
he is basically fine with what we are coming up, keeping a loose eye on
it at this final stage of development.
i got permission from to post his final evaluation of that research, so
here it is for the record:
/* HEADER */
/** floating point pixel */
typedef struct {
float r; ///< red float component
float g; ///< green float component
float b; ///< blue float component
float a; ///< alpha float component
} livido_pixel_float_t;
/** atom types */
#define LVD_ATOM_PAIR 1 ///< pair node
#define LVD_ATOM_FRAME 10 ///< physical frame holding pixel buffer (livido_pixel_float_t *)
#define LVD_ATOM_NUMBER 101 ///< number value, encoded in double
#define LVD_ATOM_STRING 102 ///< text string
#define LVD_ATOM_DATA 103 ///< pointer to custom data buffer
/** atom structs */
typedef void livido_atom_t; ///< each atom contains dynamic type description, so from C side an atom is void
typedef int livido_atom_type_t; ///< this is just an int, but we enjoy to be descriptive in api's that change the world
typedef struct {
livido_atom_type_t type; ///< LVD_ATOM_PAIR
livido_atom_t *car; ///< left branch (in list notation, NULL denotes an empty atom)
livido_atom_t *cdr; ///< right branch (in list notation, NULL denotes end of list)
} livido_atom_pair_t;
typedef struct {
int type;
double value;
} livido_atom_number_t;
/** stuff to handle atoms (data) and pairs (list traversal)
this should go in the library.
question:
setjmp is posix if i recall, so might be a good idea to
install an exception handler for livido functions. this will
keep the traversal functions much simpler and avoid lots of if-then-else nobody likes
i'll call it livido_throw here..
the argument should be the type of exception later.
*/
void livido_throw(void){}
/** type checking */
int livido_atom_type(livido_atom_t *atom) {int *t = (int *)atom; return *x;}
int livido_atom_framep(livido_atom_t *atom) {return (livido_atom_type(atom) == LVD_ATOM_FRAME);}
int livido_atom_numberp(livido_atom_t *atom) {return (livido_atom_type(atom) == LVD_ATOM_NUMBER);}
int livido_atom_pairp(livido_atom_t *atom) {return (livido_atom_type(atom) == LVD_ATOM_PAIR);}
/** data structure traversal */
livido_atom_t *livido_atom_car(livido_atom_t *atom){
livido_pair_t *p = (livido_pair_t *)atom;
if (!livido_atom_pairp(atom)) livido_throw();
return p->car;
}
livido_atom_t *livido_atom_cdr(livido_atom_t *atom){
livido_pair_t *p = (livido_pair_t *)atom;
if (!livido_atom_pairp(atom)) livido_throw();
return p->cdr;
}
/** data access
these will throw an exception if there is an error. */
double livido_atom_get_number(livido_atom_t *atom){
livido_number_t *n = (livido_number_t *)atom;
if (!livido_atom_numberp(atom)) livido_throw();
return n->value;
}
/** data allocation
this should be cons. but i don't think that is a good idea
without GC... maybe forget about allocation on the stack all together,
cuz that's quite involved actually and a good example of premature optimization...
DAMN YOU PAUL GRAHAM!!!
*/
/** this should be a callback implemented by the host */
void *livido_malloc(int size){ return malloc(size); }
/** atom constructors */
livido_atom_t *livido_atom_number(double value){
livido_atom_number_t *n = livido_malloc(sizeof(*n));
n->type = LVD_ATOM_NUMBER;
n->value = value;
return (livido_atom_t *)n;
}
/** cons is the pair constructor */
livido_atom_t *livido_atom_cons(livido_atom_t *car, livido_atom_t *cdr){
livideo_atom_pair_t *p = livido_malloc(sizeof(*p));
p->type = LVD_ATOM_PAIR;
p->car = car;
p->cdr = cdr;
return (livido_atom_t *)p;
}
/* HOST EXAMPLE */
/* PLUGIN EXAMPLE */
/*
we adopt a semi-pull method:
* all image metadata and parameters will be pushed by the host
* the image data will be pulled from the identifier.
this enables the host to do read caching for images.
likewize, the returned images could be pushed to the host
at the moment they are produced to enable write caching.
for this we define
livido_pull_frame (frame_id);
livido_push_frame (frame_id);
*/
/*
plugin will receive a list of frame packets
a frame packet is (timecode port port port ...)
port can be any valid livido atom complying to the plugin class' input template
output is returned in the same manner, complying to the plugin class' output template
image data will be handled through push-pull to allow for caching to be abstract.
*/
/* example will expect (number number image) and return (image image) */
plugin_process (livido_atom_t *param){
livido_atom_t *todolist = param; // traversal pointer
double n1, n2;
livido_image_id_t iid0, iid1, iid2; // image ids
livido_pixel_t *p0, *p1, *p2; // image pointers
livido_timecode_t tc;
while (todolist){
livido_atom_t *x = livido_atom_car(todolist); // get next port list
/* load the parameters into C local variables */
tc = livido_atom_get_timecode(livido_atom_car(x)); x = livido_atom_cdr(x);
n1 = livido_atom_get_number(livido_atom_car(x)); x = livido_atom_cdr(x);
n2 = livido_atom_get_number(livido_atom_car(x)); x = livido_atom_cdr(x);
iid0 = livido_atom_get_iid(livido_atom_car(x));
/* get the image read data */
p = livido_iid_pull(iid);
/* create new image ids */
iid1 = livido_iid_new();
iid2 = livido_iid_new();
p1 = livido_iid_pull(iid1);
p2 = livido_iid_pull(iid2);
// process *p0 -> *p1 *p2
/* push result frames to host */
livido_iid_push(iid1);
livido_iid_push(iid2);
/* free source frame */
livido_iid_free(iid0);
/* dump all the data in the result list
this will be returned in the end, but
sorry i lost motivation... */
/* advance to next port list */
todolist = livido_atom_cdr(todolist);
}
// return result list..
}
/*
conclusion: this is imnsho the way to go, definitely.
but, doing it in C like this, without the availability of a real
lisp or scheme is complete madness. especially the lack of
garbage collection (to make 'cons' do what it is supposed to do)
is a show stopper.
so, allow me to change my opinion again, and tell you to throw
it all away, or to do it in scheme (guile). i got too used to
the convenience of high level languages, that i start to think
in them. i can't use C to solve a 'general problem' any more..
maybe i never could.
i will stop working on this. the problem is simply to hard to solve
centrally. long live distributed anarchy. i will keep providing
hints and information, but no code. i'm sorry. this has clearly
showed me that central design of APIs is a problem i will never
do myself any more, unless i have total control, which i have
in packet forth. and basicly, that's the reason i am writing it,
because i can't live with compromize.
that's the reason we should all write our own plugin architectures,
and build 1-1 bridges between different plugin APIs and solve things
that don't match for the case of that 1-1 connection. central tradeoffs
suck. they only make big companies happy. homey no play that. i simply
feel bad deciding things before they occur.
like some sage said: don't define yourself, and definitely
don't define others.. i say write a language or keep it simple
(AND!!) and i'm not saying that, just doing it..
the cache mechanism is a good thing i think. we should keep that idea..
*/