Re: [buildcheapeeg] Managing a network of nodes and data streams in one thread

From: Dave (dfisher_at_pophost.com)
Date: 2002-04-02 17:19:10


On Tue, 2 Apr 2002 11:17:40 +0100, Jim Peters wrote:

>The only problem I can see is if people decide they want to do a big
>FFT every 16 samples, say, which could mess up the whole thing because
>a big FFT might take more than the time available within one sample
>period. Slower operations like this could be moved over into another
>Network in a lower priority thread (see ideas of a Bridge lower down).

<snip>

>Okay, fine. I think we have two choices -- either you inherit from
>Node, or you create a helper class which inherits from 'Node' that you
>then embed in your class instance. For example, I'm thinking about a
>'bridge' that has two Nodes, one in each of two different networks
>(probably in two different threads) that allows data streams to pass
>between different threads via a bunch of circular buffers. Obviously
>this can't inherit from Node twice (at least, not so far as I know in
>C++), so I think I'll need two helper classes, with instances of these
>classes embedded in the 'bridge' class.

That's an interesting solution, having two networks and bridge nodes between
them. What is going to get tricky is "patch-chording" between the networks.
You would not be able to have any nodes in NetworkA (the fast network) be
dependent on the output from anything in NetworkB (the slow network). Plus,
from an OO/application standpoint, we now need something which can manage all
the networks. This could get complicated. John's post has me thinking that
perhaps it would simplify things to keep data acquisition and data processing
in two separate threads, rather than try to separate slow process()'s out
piecemeal. It might make the whole system more managable?

>> Ok... except dev, fb1, fb2, and xmas would really be pointers kept
>> in a list which would then be controlled by the node-supervisory
>> code, right? The more I think about this, the more it seems like
>> there should be a Network class to handle traversal of the nodes as
>> well as adding or removing nodes from the list
>
>Yes, that is a good idea, and I'm working with that idea now.

Ironically, I implemented your idea using the embedded 'static' code in each
Node. I have stubbed out code for a BioNodeNetwork class, and then decided to
move those methods into the Node class. Either way work, and each has its
attractions for me. I really like being able to simply declare a Node anywhere
in the program, and having it automatically become a part of the node network.
The code does not need to know anything about a global reference to a
particular NodeNetwork object, just whatever nodes it wants to connect to,
which it can query from "itself" once it is in the network. Kind of
fractal-like, where the whole is encoded in the part.

But I think it would be worthwhile to go back to my feelings of separating it
out to a NodeNetwork after all, because you bring up some good points about
being able to create other Networks for other purposes, not simply for multiple
device interfaces.

>> What I would like to see is it more situated within the
>> BioDevice/BioSensor framework. This would alleviate the need for
>> node-supervisory code being in the node code (as globals or
>> whatever), and keep the flow more natural in terms of the system.
>
>I'm working on a test implementation now that doesn't use globals --
>instead every Node belongs to a Network. That means we can have
>several Networks running in different threads, or even in the same
>thread. Even if we don't actually need multiple Networks, it is a
>cleaner way to do it.

That's what I was thinking originally. I had never instantiated several
objects with shared memory, though (via static data and methods) and got
fascinated with it. :-) So it was a cool diversion and interesting for
experience, but having multiple Networks, and separating the managerial code
from the nodes themselves makes more OO sense, I think.

>> traversal of the node/port tree? If I am understanding correctly,
>> that traversal will only occur when there is data ready from the
>> external device for a particular port in the node list. Are there
>> are other situations I'm not thinking of?
>
>The only possibility is if there is more than one input stream we're
>feeding into the network through different Nodes. Anyway, I'm going
>with your idea of letting the BioDevice code control the network (or
>at least have some 'main loop' alternate between calling the BioDevice
>and running the network of nodes). As you say, this is all flexible
>enough that we can add to and improve this later if necessary.

For now, it works well there, and it makes sense to kick off the network once
data is received from an external source. Handling multiple input streams into
one Network would mean reorganizing the objects to some extent. I think it
would mean going to some kind of DeviceManager that could monitor incoming data
and fire off the Network. This would avoid having two BioDevices (or some
other source) in separate threads firing off the Network simultaneously.
Either that, or using synchronization techniques in the Network class itself to
kick it into motion.

Right now I have the Network kicked into motion in biodeviceprocompserial.cpp
as follows:

//
// We have inserted new values in some sensor Out Ports. Traverse
// the node network to update any other nodes who are watching
// those Out Ports.
//

BioNode::ProcessNodeNetwork();

This makes a call to the *class* wide method (cool :). But as I mentioned last
night, I have a lot more testing to do to see how it bears up under a
processing load. To change it in the way we are talking about above, I guess I
would have to pass the BioNodeNetwork object into BioDevice when the BioDevice
object is created. I don't think I want BioDevice to inherit BioNodeNetwork,
because other objects are going to need access to the BioNodeNetwork object.
Does this mean that the instantiated Network object is going to need to be
global? Or should it be a part of BioDevice itself? The latter only makes
sense if we are committed to there being only one input source per network.
But if not, then a Network is more autonomous...

>> If we do need to add some synchronization mechanisms, things like
>> semaphores offer low overhead for this.
>
>I think that if we are using a Network class to maintain the Nodes,
>then we don't need the globals, and we can also do without semaphores
>by using circular lock-free buffers for passing data between threads.
>(I've used these before).

Could you explain a little bit more of what you mean by circular lock-free
buffers? Do you mean having something like a two-way pipe stream between the
threads?

>> Based on prior discussions, it seemed like others were not in favor
>> of building in support for multiple devices, thus I am not sure
>> under what conditions there would be a reason to have more than one
>> producer node in the network. I would not mind having the
>> flexibility of adding such capability, but am thinking that this
>> will effect the answer to the question of who kicks off the
>> traversal of the network when new data arrives.
>
>I was thinking of work I did previously where I was handling a general
>message-passing network, where messages might arrive from all kinds of
>places. This required message generator 'nodes' to register the
>filehandles they were waiting on with supervisor code which did the
>select() call for all of them at once. We probably don't need
>anything this complex, though, right ?

I think that would only be necessary if we had multiple input sources, such as
your above network, which could receive messages from many different sources.
But I *think* that having multiple input sources for this project would be more
rare, and that it will mostly be one source per Network. Thus, I am thinking
that should there be an interest in having multiple input sources, that perhaps
a merger Network could be implemented. Perhaps something like this:

| BioDeviceNetworkA |
| BioDeviceNetworkB |
================
|
|
v
BioDataNetwork

NetworkA and NetworkB could have one set of nodes for their output. Their
process() method could simply trigger an event in BioDataNetwork which wakes it
up to then process its network. I dunno... it is certainly more
straightforward to have one input source per Network, but I like to keep my
options open, too, for future needs.

>Actually, it was not only generator nodes that required select()
>support, but also nodes that were waiting to write on blocking output
>streams. Hopefully if we allow generous buffers between threads, the
>buffers will only overflow in true processor-overload situations.

That, or make them non-blocking if we don't want to hang up our network waiting
on a write(). I think we will get into some portability issues with that,
though.

>As I've mentioned, I'm working on a test implementation of this. I'm
>coding in simple C++, and using C for strings and so on.

That's great. I had a feeling that you might be doing something like this
also. It will be great to compare notes.

>The basic structure is there, but I've been trying to think of ways to
>optimise it even further. However, I haven't found anything as
>flexible and simple as what I've already suggested. With more
>information provided by individual Nodes, certain other optimisations
>could be made, but only at the expense of additional complexity for
>both Node-writers and the Node/Network classes. So I'm sticking with
>the simpler implementation.

That sounds good. What I've got right now is the bare bones of what we've
talked about, with the addition of some descriptive strings for each
node/network.

>The whole idea is to make something fairly robust, efficient and
>flexible so that we can just forget about it and get on with using it
>to build more interesting things.

That would be *great*. It was very easy to drop in place of what I had done
with streams and sockets. Plus, I can bring those streams back if I want to
using the process() calls. So, at a lower level, things are much simpler with
less overhead. And that's *great*. You've seemed to come up with the best of
both worlds. Let's keep working the design and see what we flesh out.

Dave.



This archive was generated by hypermail 2.1.4 : 2002-07-27 12:28:43 BST