Producing No Output
If a node function wants to cancel the flow to its successors it can do that by returning a non-zero result. This -- in effect -- means that the execution of that node function encountered an error. If there is an error handler for that node, it will be called -- but passed the input to the node that threw the error. The error handler node has a chance to re-inspect the input to the failed node function, and take remedial action:
Node Foo (const char * type) => (int type_id); Node Oops (const char *) => (); handle error Foo -> Oops;
The C++ code for the Foo node might check a static look-up table for a matching entry and return an error when no entry is found (causing no successor events to run, but rather having an Oops node event process the input instead):
int Foo(const Foo_in *in, Foo_out *out, Foo_atoms *) { static struct { const char * t, int tid } lookup[] = { { "apple", 1 } , ... , { 0, 0 } }; int res = -1; // indicates not found - its an error for(size_t i = 0; lookup[i].t; ++i) { if(0 == strcmp(lookup[i].t,in->type)) { res = 0; out->type_id = lookup[i].tid; break; } } return res; }
If no error handler is declared for a node, then no error node event is scheduled to run (meaning the error is ignored).
Producing More Output
In order to have a node produce more than a one output structure (leading to many successor events), there is a C++ help er gadget called oflux::PushTool<> which gives the node function access to this capability. Here is an instance of its use:
Modifying the flow above to have the Foo node "splay" all matching outputs in the lookup table (rather than just the first one) by adding a (non-compulsory) comment:
node Foo(const char * type) => /*splay*/ (int type_id);
And changing our C++ implementation of Foo as follows:
Foo(const Foo_in *in, Foo_out *out, Foo_atoms *) { static struct { const char * t, int tid } lookup[] = { { "apple", 1 } , ... , { 0, 0 } }; size_t out_count = 0; oflux::PushTool<Foo_out> ptool(out); for(size_t i = 0; lookup[i].t; ++i) { if(0 == strcmp(lookup[i].t,in->type)) { ++out_count; ptool->type_id = lookup[i].tid; ptool.next(); } } return out_count ? 0 : -1; }
Now Foo could produce several outputs (type_ids) on one input, and each of those will get processed using the flow that follows node Foo. If no matches are found in the lookup, then no flow will operate (no successor node events).
Summary
In some cases where it is advantageous to release multiple outputs from a node (causing more successor events to be created), the run-time can be leveraged to dispatch the processing of those events on multiple threads. More threads doing work means more concurrency. The trade-off is that the successor event work must be more (in single thread terms) than the over head incurred (in the run-time) by doing this. It may be that successor processing happens so quickly in a single thread, that having Foo just output a container (e.g. vector) containing all results is better.
No comments:
Post a Comment