If you want the nodes in your tree to do any useful work, you are going to need to get your executable code out to them, and that means writing an SHWorker subclass. SHWorkers aren't very complicated; they are like BLoopers, but even easier to use! Note that SHWorkers inherit from the SHComponent class; this gives them some handy methods they can call, as well as some pure virtual methods that they must implement.
SHWorkers, like BLoopers, have a MessageReceived() method that is called whenever a BMessage arrives for them. Unlike BLoopers, however, SHWorkers follow normal C++ object semantics--it is safe to put an SHWorker on the stack if you want to, and you can delete an SHWorker using the delete operator instead of calling Quit(). Instead of a Run() method, SHWorkers have Start() and Stop() methods. And you can't call PostMessage() on an SHWorker to send it a BMessage--instead, you call GetMessenger() on it to get a BMessenger that targets the SHWorker, and then call SendMessage() on the BMessenger.
The methods you are required to implement in your SHWorker subclass are few. They are:
You must create a constructor for your class that can create an object from the data contained in the passed-in BMessage. This constructor essentially does the opposite of your Archive() method; instead of converting an object to a BMessage, it converts a BMessage back to an object. Be sure to call SHWorker(archive) in the initialization list!
This static method (part of the BArchivable API) is also used to turn BMessages back into real objects, and should be implemented like this:
This method (part of the SHDistributableObject API) should be overridden to return an SHFileSpec that indicates where the add-on executable file(s) for this class can be found. Like BArchivable::Archive(), each subclass should call the GetAddOnSpec() method of its parent class; in this way the SHFileSpec gets built up into a list of all the files needed to instantiate the object.
This method (part of the SHComponent API) should be overridden to supply an identifying text string for your SHWorker object. There are no conditions imposed on what this name is, in particular it is not required (by SockHop) to be unique in any way. This name can be used to identify the SHWorker in certain situations, however, so it's not a bad idea to make each SHWorker's name unique.
This method must be overridden to store all relevant state for your SHWorker into (archive). This method helps fulfill the SHWorker's BArchivable interface, and is how your SHWorker object gets transported over the TCP streams. At a bare minimum, your Archive() method should look like this:
One more requirement is placed on SHWorkers--they must call the Stop() method at the beginning of their destructor method. This is necessary to avoid race conditions during the destruction of the SHWorker object. If you forget the Stop() call, you will get warning messages printed to stdout, and the SockHop node may crash.
This, of course, is equivalent to the standard BLooper method to handle incoming BMessages. You don't have to implement it, but if you don't your SHWorker won't do very much that is interesting!
This method can be used as an optimization, to cut down on the number
of BMessages sent to your SHWorker. Before sending any BMessage to
your SHWorker, the SockHop node thread calls this method to see if your
SHWorker wants it. The default implementation of this method just
returns true; which is to say, by default an SHWorker is "interested in"
every kind of BMessage. If you wish, you can override this method
to return false for BMessages that your SHWorker can't use. That
will save the overhead of sending a BMessage that will be ignored anyway.
It is important to remember, however, that IsInterestedIn() will be called
from a thread other than the SHWorker's thread, and that by default no
locking occurs to synchronize access to the data in your SHWorker object.
Thus, you may need to serialize access yourself (by calling Lock() and
Unlock()) in this method.