Messaging Signals/Slots

Started by
3 comments, last by lpcstr 12 years, 11 months ago
I'm interested in using messages extensively in my game, and I read about how Boost.Signals is probably the slowest of the Signals/Slots library. Libcsig++ is also recommended, but test show it to only be about twice as fast, so I decided to write my own. Here is what I have so far.

Note: This makes use of auto keyword and variadic templates, both of which are available in compilers with full or moderate C++0x support. I also have a FOREACH macro, which is not supplied. It simply iterates through a container.

Delegate.hpp
[source lang="cpp"]
#include "detail/Delegate.hpp"

template <typename Signature>
class Delegate
{};

template <typename R, typename... P>
class Delegate<R(P...)>
{

public:
Delegate(R(*FuncPtr)(P...))
{
new (&base) detail::FunctionDelegate<R(P...)>(FuncPtr);
}
template <typename Object>
Delegate(Object &object, R(Object::*MemPtr)(P...))
{
new (&base) detail::ObjectDelegate<Object, R(P...)>(&object, MemPtr);
}
R operator()(P... values)
{
return reinterpret_cast<detail::BaseDelegate<R(P...)>*>(&base)->Invoke(values...);
}
bool operator==(const Delegate<R(P...)> &rhs)
{
return reinterpret_cast<detail::BaseDelegate<R(P...)>*>(&base)->Equals(reinterpret_cast<const detail::BaseDelegate<R(P...)>*>(&rhs.base));
}
private:
char base[sizeof( detail::ObjectDelegate<class GenericClass, void()> )];
};
[/source]

detail/Delegate.hpp
[source lang="cpp"]
template <typename Signature>
class BaseDelegate
{};

template <typename Signature>
class FunctionDelegate : public BaseDelegate<Signature>
{};

template <typename Object, typename Signature>
class ObjectDelegate : public BaseDelegate<Signature>
{};

template <typename R, typename... P>
class BaseDelegate<R(P...)>
{
public:
virtual R Invoke(P... values) = 0;
virtual bool Equals(const BaseDelegate<R(P...)>* rhs) = 0;
};

template <typename R, typename... P>
class FunctionDelegate<R(P...)> : public BaseDelegate<R(P...)>
{
public:
typedef R(*CallbackType)(P...);
FunctionDelegate(CallbackType ptr)
: mptr(ptr)
{}
R Invoke(P... values)
{
return mptr(values...);
}
bool Equals(const BaseDelegate<R(P...)>* rhs)
{
auto p_rhs = static_cast<const FunctionDelegate<R(P...)>*>(rhs);
return (mptr == p_rhs->mptr);
}
private:
CallbackType mptr;
};

template <typename Object, typename R, typename... P>
class ObjectDelegate<Object, R(P...)> : public BaseDelegate<R(P...)>
{
public:
typedef R(Object::*CallbackType)(P...);
ObjectDelegate(Object *object, CallbackType MemPtr)
: mObjectPtr(object), mMemPtr(MemPtr)
{}
R Invoke(P... values)
{
return (mObjectPtr->*mMemPtr)(values...);
}
bool Equals(const BaseDelegate<R(P...)>* rhs)
{
auto p_rhs = static_cast<const ObjectDelegate<Object, R(P...)>*>(rhs);
return (mObjectPtr == p_rhs->mObjectPtr) && (mMemPtr == p_rhs->mMemPtr);
}
private:
Object *mObjectPtr;
CallbackType mMemPtr;
};
[/source]

Event.hpp
[source lang="cpp"]
template <typename Signature>
class Event
{};

template <typename R, typename... P>
class Event<R(P...)>
{
public:
typedef Delegate<R(P...)> CallbackType;
Event() {}
void Add(CallbackType func)
{
Callbacks.push_back(func);
}
template <typename Object>
void Add(Object &obj, R(Object::*MemPtr)(P...))
{
Callbacks.push_back(CallbackType(obj, MemPtr));
}
void Remove(CallbackType func)
{
Callbacks.erase(std::remove(Callbacks.begin(), Callbacks.end(), func));
}
template <typename Object>
void Remove(Object &obj, R(Object::*MemPtr)(P...))
{
Callbacks.erase(std::remove(Callbacks.begin(), Callbacks.end(), CallbackType(obj, MemPtr)));
}
R operator()(P... values)
{
FOREACH(Callback, Callbacks)
{
(*Callback)(values...);
}
}
private:
std::list<CallbackType> Callbacks;
};
[/source]

Example usage (similar to boost)


Event<void()> myEvent;
myEvent.Add(&SomeFunction);
myEvent.Add(someObject, &SomeObject::MemberFunction);
myEvent();


My initial testing of this has been with GCC 4.5.1. With default optimization, my Event class was 18x faster than a Boost.Signal, only testing for invocation speed. With -O2 optimization, it was about 10x faster.

What do you think? Any suggestions?
Advertisement
Because you want to use messages *extensively*, I bet you will get this situation: in one message handler, the handler want to remove itself, or some other handlers from the signal.
Then you will get crash... At least libsigc++ should have deal with that situation.

And yes boost::signal is slow, but libsigc++ is fast enough for average use. If there are about 1000 messages are passed around in one frame, libsigc++ should not be bottleneck for you (I did test).
If you need 10K+ messages each frame, most likely your core code is depending on messages, that should be avoided. Instead of messages, talking between objects directly may be better.

I would like to quote myself's word from other reply in your another topic,
"Sometimes reinventing the wheels may be not bad, you can learn much from it. But if you decide to reinvent, reinvent some featured wheels, so you won't get trouble when your application goes more complicated in the future."

PS: This kind of topic should better be posted in "general" forum, since it's not very game specified.

https://www.kbasm.com -- My personal website

https://github.com/wqking/eventpp  eventpp -- C++ library for event dispatcher and callback list

https://github.com/cpgf/cpgf  cpgf library -- free C++ open source library for reflection, serialization, script binding, callbacks, and meta data for OpenGL Box2D, SFML and Irrlicht.


Because you want to use messages *extensively*, I bet you will get this situation: in one message handler, the handler want to remove itself, or some other handlers from the signal.
Then you will get crash... At least libsigc++ should have deal with that situation.


I don't quite understand what you mean. What situation are you talking about?
SomeSignal;

SomeSignal.add(MyHandler);

void MyHandler()
{
SomeSignal.remove(MyHandler); // maybe crash
SomeSignal.remove(the handler that will be processed next in FOREACH); // maybe crash
}

https://www.kbasm.com -- My personal website

https://github.com/wqking/eventpp  eventpp -- C++ library for event dispatcher and callback list

https://github.com/cpgf/cpgf  cpgf library -- free C++ open source library for reflection, serialization, script binding, callbacks, and meta data for OpenGL Box2D, SFML and Irrlicht.

[source lang="cpp"]
void Remove(CallbackType func)
{
ToBeRemoved.push_back(func);
}
template <typename Object>
void Remove(Object &obj, R(Object::*MemPtr)(P...))
{
ToBeRemoved.push_back(CallbackType(obj, MemPtr));
}
R operator()(P... values)
{
if ( !ToBeRemoved.empty() )
{
FOREACH(Callback, ToBeRemoved)
{
Callbacks.remove(*Callback);
}
ToBeRemoved.clear();
}
FOREACH(Callback, Callbacks)
{
(*Callback)(values...);
}
}
private:
std::list<CallbackType> ToBeRemoved;
[/source]

No noticeable change in performance. My test puts it at 200M invocations of Event::operator(), with two callbacks registered.

This topic is closed to new replies.

Advertisement