Sign in to follow this  
lpcstr

Messaging Signals/Slots

Recommended Posts

lpcstr    127
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.

[b]Delegate.hpp[/b]
[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]

[b]detail/Delegate.hpp[/b]
[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]

[b]Event.hpp[/b]
[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)

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

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?

Share this post


Link to post
Share on other sites
wqking    761
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.

Share this post


Link to post
Share on other sites
lpcstr    127
[quote name='wqking' timestamp='1305957381' post='4813768']
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.
[/quote]

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

Share this post


Link to post
Share on other sites
wqking    761
SomeSignal;

SomeSignal.add(MyHandler);

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

Share this post


Link to post
Share on other sites
lpcstr    127
[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.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this