Suppose you decided that you want to write a module to produce sound asynchronously. Suppose its interface looks like that:
interface SomeModule : SynthModule { async out byte stream outdata; };Fine. So how exactly do you send something now?
With asynchronous streams you send the data as packets. That means, you send individual packets with bytes in the above example. The actual process is: allocate a packet, fill it, send it.
So step by step:
DataPacket<mcopbyte> *packet = outdata.allocPacket(100);
// cast so that fgets is happy that it has a (char *) pointer char *data = (char *)packet->contents; // as you can see, you can shrink the packet size after allocation // if you like if(fgets(data,100,stdin)) packet->size = strlen(data); else packet->size = 0;
packet->send();
That other way is pull delivery. You say: please, I want to send packets as fast as the receiver is ready to process them. You start with a certain amount of packets you send. And as the receiver processes one packet, and another, you start refilling them with fresh data, and send them again.
You start that by calling setPull. So for instance
outdata.setPull(8, 1024);This means that you want to send packets over outdata. You want to start sending 8 packets at once, and as the receiver processes some of them, you want to refill these.
Then, you need to implement a method which fills the packets, which could look like that:
void request_outdata(DataPacket<mcopbyte> *packet) { packet->size = 1024; // shouldn't be more than 1024 for(int i = 0;i < 1024; i++) packet->contents[i] = (mcopbyte)'A'; packet->send(); }Thats it. When you don't have any data any more, you can start sending packets with zero size, which will stop the pulling.
Its essential to call that method exactly request_<streamname>.
Thats much simpler. Suppose you have a simple ToLower filter, which simply converts all letters in lowercase:
interface ToLower { async in byte stream indata; async out byte stream outdata; };Thats really simply to implement. Here is the whole implementation.
class ToLower_impl : public ToLower_skel { public: void process_indata(DataPacket<mcopbyte> *inpacket) { DataPacket<mcopbyte> *outpacket = outdata.allocPacket(inpacket->size); // convert to lowercase letters char *instring = (char *)inpacket->contents; char *outstring = (char *)outpacket->contents; for(int i=0;i<inpacket->size;i++) outstring[i] = tolower(instring[i]); inpacket->processed(); outpacket->send(); } }; REGISTER_IMPLEMENTATION(ToLower_impl);
Again: its essential to call the method exactly process_<streamname>.
As you see, for each arriving packet you get a call for a function (the process_indata call in our case). You need to call the processed() method of a packet to indicate you have processed it.
Hint: if processing takes longer (i.e. if you need to wait for soundcard output or something like that), don't call processed immediately, but store the whole data packet and call processed only as soon as you really processed that packet. That way, senders have a chance to know how long it really takes to do your work.
As synchronization isn't so nice with asynchronous streams, you should use synchronous streams whereever possible, and asynchronous streams only when necessary.