Signals

I wanted to have a signal/events mechanism in my engine, so first i looked up some libraries which are already implemented – less code to write and maintain is always better. I liked the Qt signal/slot mechanism, but it is slow(string based) and needs the code to be preprocessed by Qt toolchain. It works nice for GUI apps, but it’s useless when it comes to games. The second choice was libsigc++, the library used by gtk. It’s much nicer in terms of speed, and also does not require any code preprocessing, but has one big flaw: it requires the class to derive from the sigc::trackable object. This can be omitted by the usage of sigc::connection objects returned by the connect function. This in turn requires to manage those connections manually, either storing them in a container or having them as members in the given receiver class. For me this is too much hassle.

In the end I decided to implement my own signals. The goal was the ease of use of the Qt signals with the speed and native C++ implementation as in libsigc++. Since I already use FastDelegate through out my code, there was no need for implementing a delegate mechanism. The only thing that had to be done was the signals themself. The signal functionality can be divided into two categories. Emitting the signals and connecting signal handlers. The first part is pretty obvious. I have a separate template signal class depending on the number of arguments. For now I implemented signals for methods/functions with 0 up to 3 arguments(if I need a signal taking more arguments it’s pretty much copy+paste).

The second type of functionality, connecting handlers, is a little more complicated. The most important thing is that when the instance of the object connected to the given signal gets destroyed, the signal should remove the callback from it’s list of handlers. For this, C++ has a killer feature: destructors. The simplest approach, used in libsigc++, is to create a connection object that will disconnect the callback from the signal when it gets destroyed. As I stated earlier I don’t like the need to have to manage those connections manually. The solution of course is a base class(again this approach is used in libsigc++), which will manage all the connections of the derived class. The problem arises when you have a class that already derives from another class. You could use virtual inheritance but this is what I would like to avoid at all cost.


My solution was to implement SignalConnector class that would manage all connections. Is is something like sigc::trackable, but you don’t need to derive from it, you can have it as a member in the handler class. There is also a interface ISignalHandler that has one abstract method signal_connector() which returns the reference to the objects SignalConnector. As a cherry on top, I implemented simple SignalsAwareObject base class which in turn implements ISignalHandler. The signals can be connected directly using the SignalConnector, or one of the helper template methods implemented in ISignalHandler. This way there are 3 options when it comes to deciding how the class will handle signals:

  • Manually using SignalConnector class – most troublesome choice and offers no benefits.
  • Implement ISignalHandler interface – When the class can’t derive from SignalsAwareObject. This gives the access to all of the helper methods defined in ISignalHandler, but still the class have to have it’s own SignalConnector and return it through signal_connector() method.
  • Derive from the SignalsAwareObject – The best choice if applicable. No connection management required.

Here is a full example of usage(all 3 methods are shown):

#define _B(METHOD) fastdelegate::bind(&METHOD, this)

void foo(int val){
  std::cout << "foo(" << val << ")" << std::endl;
}

struct Test1{
    Test1()           {    }
    virtual ~Test1()  {    }

    void connect(Signal &signal){
      m_connector.connect(signal, _B(Test1::some_callback) );
    }

    void some_callback(int val){
      std::cout << "Test1::some_callback(" << val << ")" << std::endl;
    }
  private:
    SignalConnector m_connector;
};

struct Test2: public ISignalHandler{
    Test2()           {    }
    virtual ~Test2()  {    }

    void some_callback(int val){
      std::cout << "Test2::some_callback(" << val << ")" << std::endl;
    }

    const SignalConnector& signal_connector() const{ return m_connector; }
    SignalConnector m_connector;
};

struct Test3: public SignalsAwareObject{
  Test3()           {    }
  virtual ~Test3()  {    }

  void callback(int val){
    std::cout << "Test2::some_callback(" << val << ")" << std::endl;
  }
};

void test_connections(){
  using namespace nstd;
  {
    Signal    OnEvent;
    connect(OnEvent, &foo);
    {
      Test1 t1;
      Test2 t2;
      Test3 t3;
      connect(OnEvent, &t2, &Test2::some_callback);
      connect(OnEvent, &t3, &Test3::callback);
      t1.connect(OnEvent;);
      OnEvent(1);
    }
    OnEvent(2);
  }

  {
    Test1 t1;
    Test2 t2;
    Test3 t3;
    {
      Signal  OnEvent;
      connect(OnEvent, &t2, &Test2::some_callback);
      connect(OnEvent, &t3, &Test3::callback);
      t1.connect(OnEvent);
      OnEvent(3);
    }
  }
}
Advertisement

Tags: , , ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.