Signal Brainstorming

Published May 01, 2007
Advertisement
In an effort to solve my signal/messaging system problem, I'm going to attempt to work through the problem in a journal entry. I could just keep the explanation all off-line and talk through the problem with the bat I keep on top of my computer to ward off glitches, except unlike the journal when the bat answers back it doesn't solve my problems, it means my problems are just beginning.

I'll use the signal/slot terminology in my explanation even though in practice I'll be using these for signals, events and messages. As I've explained before I'm not that sure of the difference between the three but I'm fairly sure the implementation should be the same.


Functionality required by the System

The functionality I need is a signal system that can communicate information between modules in a decoupled way - i.e. without tying their implementation too closely together. I need the following types of signals:
  1. A way to link in zero to N slots to a signal, so the equivalent of a functional call to the signal will be sent to zero to N destinations. In this approach each slot should operate independently; it should not matter which order the slots' functions are called
  2. A way to link in zero to N slots to a signal, but with an ordering priority given to each one. under this approach each slot will return a boolean flag to determine if the signal has been dealt with (this is more of a message approach, I guess). The slots need to be ordered by priority.
  3. A way to link in zero to one slots to a signal. Essentially this is similar to a function pointer. Useful for when there is only one destination that makes sense, although the destination may be undefined.


Out of all of these the second version is the most complicated; the other two are simpler subsets; hence it will be this version that I will attempt to find a good solution for.


Inside a signal system

The basic functionality of a signal system is not that difficult to implement:

  • A method to connect up slots to the signal, storing something like a function pointer in a list like structure.
  • A method to disconnect the slot (remove it from the list)
  • A method to send a function call, possibly with arguments, to all the slots on the list


The complicated elements are in the details.

  • If your function calls have return values, how are they combined to pass back to the caller of the signal?
  • In an object oriented code base, the slot function calls are likely to be member functions, which requires a member function pointer and a pointer to the object; they're a lot more finnicky to work with
  • If a slot does not disconnect from the signal you can be left with a dangling function pointer and potential disaster
  • If this is a general signal type class, then you will need to deal with any variety and number of parameters to your function calls


I'll go through each of these and see how they apply to my situation.

Function return values

This isn't that much of a problem in my case. Nearly all my signals do not need to rely on return values; if a rare one does, I could simply pass a pointer or reference into the function and leave it to the calling functions to deal with setting up appropriate values. Admittedly this isn't the most elegant solution, but I'm sure the need to send

information back to the caller isn't that common in my situation. I can leave the return values void, or have boolean flags for the case where slots can "deal" with a message from a signal and stop it being passed to lower priority slots.

Member function pointers and pointer to objects

This one is a bit trickier as I haven't had much experience with member function pointers. I'm not that sure if I can get around having to provide a class type, i.e.:
void (MyClass::*classPointer)()
If the pointer has to belong to a specified class it will make it ugly to define the member function pointer unless I can derive from a base class.

As an alternative to a base class (if it doesn't work) I could get away with a static member function that takes a copy of the object and then calls a proper member function, i.e.:
static void MyClass::CallFunction(MyClass *object, int variable) { object->Function(variable); }
But that's an ugly kludge.

Ensuring slots are disconnencted

This one is important. If the slot function's object is destroyed then it must be disconnected from the signal, otherwise when the signal is called then anything could happen (most likely something bad!). There's two ways I can deal with this problem.

The easy way is to put the onus on the caller and make it policy that every slot has to disconnect before or as it is destroyed. Although this isn't very object oriented I could see this as reasonable if all the signals were connected during initialisation internal to an object; then it's easy to make the corresponding disconnect in the destructor. However it limits the functionality to this case; if I wanted to for example have an object link a child object up to a signal then it gets very complicated as to when and where to make the disconnect; annoying errors will happen.

The robust way is to have each slot object have a list of what signals it is connected to and on destruction go through the list to automatically disconnect. A good way to do this is through a base class that object that wish to act as slots must derive from; this base class can contain the variables and destructor needed to do this automatic disconnect. This is how the sigslot library works.

However the downside of the robust method as used by sigslot is it must allocate a new chunk of memory for each connection made to add to this list stored in the connecting object. The amount of memory used isn't that great, but I have an adversion to dynamically allocating and deallocating memory willy-nilly within my code; it slows down the connection code and I fear for memory leaks. I'm not that knowledgable about how the memory allocator works in C or C++ so I don't know if my policy is sensible or not. I prefer to allocate a pool of memory at a go for performance sensitive things and then allocate chunks when needed.

I'll have to keep this in mind when I brainstorm through a potential solution at the end of this journal post.

How to deal with generic argument types and number of argument

While the C programmer in me still has fond memories of type casting, I know the proper C++ method is to use templates. The generic argument types shouldn't be that much of an issue if I wasn't such a muggins at C++.

However things get ugly really fast when dealing with generic numbers of arguments in the function calls. As far as I know C++ doesn't have a good way of dealing with that. From what I've seen in both Boost::signals and sigslot the only way to deal with this problem is write out an individual signal class for each number of variables you want to support; i.e. a class for zero arguments, a class for one argument, and so on up to the maximum you wish to support.

If I can get away with it I should consider having a fixed number of variable types. For most of the signal types I think I need I basically only require a handful of argument types at most; occasionally I need an integer or floating point variable be passed, and maybe once in a while a reference or pointer to a structure.


Building the signal and slot connection lists

Now I've considered the challenging points of the signal and slots, it's time to start thinking about a solution. Given I tend to think in pseudo-code and will probably have a C bias in my thinking I'll just stick to the basic data structures and program flow and figure out a way to make it work in terms of code later.

Part of the problem I see with the sigslot approach is that each signal needs to have a list of all the connected slots, and each slot needs a list of the connected signals. Given that when a slot needs to disconnect or a signal is destroyed then both lists need to be updated it makes sense to try to combine both into a single data structure.

A sample data element could contain:
  • function pointer for the connected slot, plus member object (maybe combined together in a functor?)
  • integer or float for priority of slot (used for priority sorted signals)
  • pointers to connect up signal list, i.e. slots connected to a signal
  • pointers to connect up slot list, i.e. signals connected to slot


With this structure the element is acting like a double list (as distinct from a doubly linked list); if the element is removed during a disconnect then both the signal and slot lists need to be repaired.

The interesting bit is if I can make each data element contain the same types and be the same size. Then I could store all the elements in a memory pool, possibly for every signal. I could allocate removed elements to a "dead" pool to be reused without having to free memory, and link up both the signal and slot lists from within the pool. I'm not knowledgable enough about hardware to know if there's a downside to having all the signals memory in a big chunk, but it doesn't sound too difficult from a software perspective - although I would need a global memory manager for the signals. Also if I make the pool a std::vector I'd have to store the pointers in a way that wouldn't be invalided through a resize, such as integer offsets from the start of the array.

Now I need to figure out what the format of this data type should be to see if it is possible. This will require some reading up on the capabilities of C++ as I'm too vague on how this could be achieved. Hopefully soon I can figure out some proper pseudo-code and I can make a test implementation.
Previous Entry Stuck before I begin
Next Entry Signal Processing
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement
Advertisement