Message dispatching system - big problem with polymorphism

Started by
5 comments, last by noizex 12 years, 1 month ago
Hello,

First of all, I come from a bit higher level languages that can handle runtime decisions
pretty well and are easy to work with, so I'm having a lot of troubles in C++. Something
that seemt easy at first, turned out to be either impossible, or requiring some black
magic to achieve. Consider a simple network message dispatcher that receives a packet,
reads its type and depending on it creates a wrapper that contains specific data - each
network message would have a bit different data (sorry for complete pseudocode but I
don't have anything written yet because I hit the wall early on design stage and its
pointless to code when I know it won't work as I supposed it would..)


enum {
MSG_LOGIN,
MSG_DISCONNECT,
...
} MessageType;

// Base class
class Message
{
MessageType msgType;

MessageType GetMessageType();
SetMessageType(MessageType type);

SendMessage(); // sends message over the network
}

// Derived message class
class LoginMessage: public Message
{
string login;
string pass;

LoginMessage(Bitstream data)
{
login = data.ReadString(20);
pass = data.ReadString(20);
}
}

while (incoming packets)
{
// This creates message wrapper class that reads data from packet and sends
// it to factory method to create a specific message (LoginMessage, ConnectMessage)
Message* msg = msgfactory.CreateMessage(packet.type, packet.data)

// Publish it to the dispatch system, for now we just need to know that message
// will be send to one or more handlers which registered to receive messages of that
// type
MsgQueue.Publish(packet.type, msg);
}


Publish sends that message to all registered handlers, for example if packet.type is
MSG_LOGIN it would be handled by LoginManager::HandleLoginRequest (it registers itself
as a listener for this particular message type, but that part of code is irrelevant).

Lets look at this message receiving method:


LoginManager::HandleLoginRequest(Message* msg) // this so we can receive Message* from
// factory, but in reality this method is
// only interested in LoginMessage*
{
// PROBLEM: msg is base class so it doesn't have any knowledge of derived class
// members that I want to use

if (msg.login && msg.password) { ... } // won't work :(

// handle the message logic
}


HandleLoginRequest method will only be called with LoginMessage instances, because it
registers to receive only them, but I don't know how to make it work with:

HandleLoginRequest(LoginMessage* msg)

when I receive Message* from factory.

I could use dynamic_cast<LoginMessage*> at beginning of hanlder function to downcast
received base class to its derived class, but I only heard bad things about it, and
it doesn't seem clean anyway to do it for every handler method that receives message,
even if I'm sure that this message will be a certain derived class.

I find it very hard to write things that are decided on runtime in C++ - there is
always some problem.

There will be many messages each having its own wrapping class so I don't really
want to use enormous switch statement that will be able to determine on compile time
things like what class needs to be created.

Same problem I've encountered when I wanted simple event system, with each event
having its EventArgs class that can be derived to contain more specific information
(for example for DamageEvent it will be amount of damage, attacker etc., for DieEvent
it could be killer). Then again, events are usually dispatched in a manner:

HandleEvent(Event& e, EventArgs& a)

which again destroys the idea because if I derive MyEventArgs from EventArgs and I use
it together with my event, when it arrives at handler it wont be MyEventArgs anymore,
I will loose that additional part of data I needed.

Is there some clever workaround or I just do things in a non-C++ way? I hope I described the problem well enough, if not let me know and I will try to write something more than pseudo-code.

Where are we and when are we and who are we?
How many people in how many places at how many times?
Advertisement
I've used something like this in a similar situation:


class MessageHandler { };
class LoginMessageHandler : public MessageHandler
{
virtual void HandleLoginMessage(LoginMessage* msg) = 0;
};
class Message
{
...
virtual void Deliver(MessageHandler* handler) = 0;
};

class LoginMessage : public Message
{
...
void Deliver(MessageHandler* handler)
{
LoginMessageHandler* h = dynamic_cast<MessageHandler*>(handler);
if (h != 0) h->HandleLoginMessage(this);
}
};


There are probably other ways to solve this problem, but the advantages with this solution are:
- it uses proper polymorphism instead of smelly switch statements
- although it uses casting, the cast statement is hidden within the corresponding type instead of at the point of usage

openwar - the real-time tactical war-game platform

There is nothing wrong with using dynamic_cast when you need to downcast. You could also use static_cast in certain situations as well. Most of the time when you hear people bashing things like dynamic_cast they're either A) too stupid to know how to use it right anyways, or B) making claims about performance that they haven't bothered to verify with a profiler.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.

It can be solved using double dispatching, but suffers from 3 problems:
1. The base class of handlers requires to know each type of message.
2. The base class of messages requires to know each type of handlers.
3. There are 3 method invocations involved from which 2 are virtual.

IMHO especially issue 1. and 2. drive this kind of solution practically somewhat useless. However, for the sake of completeness, the solution would look like so:

With the base class of message handlers there is a dispatch method that is agnostic of the concrete type of message. It does nothing more than invoking one of the dispatch methods of the given message (see further below). The base class also has an empty routine for each concrete type of message that should be handled.

class MessageHandler {

// dispatcher
void dispatch( Message& message) {
message.dispatch( *this );
}

// handlers
virtual void handle( LoginMessage& message ) {}
virtual void handle( LogoutMessage& message ) {}

virtual void handle( DamageMessage& message ) {}
...
};


A concrete handler just overwrites the methods it is designed to handle:

class LoginMessageHandler
: public MessageHandler {

virtual void handle( LoginMessage& message ) {
do something
}

virtual void handle( LogoutMessage& message ) {
do something
}


};


The base class of messages requires an empty method for each concrete type of handler.

class Message {

// dispatching
virtual void dispatch( LoginManager& manager ) {}
virtual void dispatch( DamageManager& manager ) {}
....

};


Concrete types of messages have to overwrite the one method that uses the belonging handler.

class LoginMessage
: public Message {

virtual void dispatch( LoginHandler& handler ) {
handler.handle( *this );
}

};


Then invoking

handler->dispatch( message );

for each registered handler would be sufficient.
It can be solved using double dispatching, but suffers from 2 problems:
1. The base class of messages requires to know each type of handlers.
2. There are 3 virtual method invocations involved for each resolved dispatching, and 2 virtual method invocations for each unresolved dispatching.

The solution works by making the concrete types of handler and message known each other in a stepwise manner. It would look like so:

In the base class of message handlers there is a pure virtual dispatch method that is agnostic of the concrete type of message.

class MessageHandler {

// dispatcher
virtual void dispatch( Message& message ) =0;

};


A concrete handler overwrites the dispatching method and adds handlers for each type of message it is designed for:

class LoginMessageHandler
: public MessageHandler {

virtual void dispatch( Message& message ) {
message.dispatch( *this );
}

virtual void handle( LoginMessage& message ) {
do something
}

virtual void handle( LogoutMessage& message ) {
do something
}

};


The base class of messages requires an (empty) method for each concrete type of handler.

class Message {

// dispatching
virtual void dispatch( LoginMessageHandler& handler ) {}
virtual void dispatch( DamageMessageHandler& handler ) {}
....

};


Concrete types of messages have to overwrite the one method that uses the belonging handler.

class LoginMessage
: public Message {

virtual void dispatch( LoginMessageHandler& handler ) {
handler.handle( *this );
}

};


Then invoking

handler->dispatch( message );

for each registered handler would be sufficient.


EDIT: The mysterious "manager" types are replaced by the intended "MessageHandler" types in the dispatching routines of Message.
There's a way by using typeid/type_info and a map, which essentially boils down to map<type_info, handlerFunction>. While the implementation isn't overly pretty, the usage looks clean, for example

eventSystem.register(handlerFunction);

Using templates, the type of the handled event is automatically determined (because it's the parameter of the handler function). You will however not be able to use handler functions for any base classes, ie. you can't say handlerFunction(BaseMessage*) and have it handle all kinds of types. typeid will always compare the actual types.

In combination with boost::bind or a selfmade replacement it should work just fine.

You probably don't want to use it as is, because only one function per event type is kind of silly. You can use a multimap instead and the unregister function will require to pass the actual function you want to remove. Also, a function to remove all handler functions that are members of a specific object might be helpful.

Also, this code is mostly based on code I found online. I can't figure out where, but googling for TypeInfo wrapper events tells me that quite a few people used the same source...


class TypeInfo
{
public:
explicit TypeInfo(const type_info& info) : _typeInfo(info) {}

bool operator < (const TypeInfo& rhs) const {
return _typeInfo.before(rhs._typeInfo) != 0;
}

private:
const type_info& _typeInfo;
};

class Event
{
protected:
Event() {}
virtual ~Event() {}
};

class HandlerFunctionBase
{
public:
virtual ~HandlerFunctionBase() {}
void exec(const Event& event) {call(event);}

private:
virtual void call(const Event&) = 0;
};

template < class T, class EventT >
class MemberFunctionHandler : public HandlerFunctionBase
{
public:
typedef void (T::*MemberFunc)(const EventT&);
MemberFunctionHandler(T* instance, MemberFunc memFn) : _instance(instance), _function(memFn) {}

void call(const Event& event) { (_instance->*_function)(static_cast< const EventT& >(event)); }

private:
T* _instance;
MemberFunc _function;
};

template < class EventT >
class StaticFunctionHandler : public HandlerFunctionBase
{
public:
typedef void (*Func)(const EventT&);
StaticFunctionHandler(Func Fn) : _function(Fn) {}

void call(const Event& event) { _function(static_cast< const EventT& >(event)); }

private:
Func _function;
};

class EventHandler
{
public:
~EventHandler()
{
for ( Handlers::iterator it = _handlers.begin(); it != _handlers.end(); ++it )
delete it->second;
_handlers.clear();
}

void handleEvent(const Event& event)
{
Handlers::iterator it = _handlers.find(TypeInfo(typeid(event)));
if( it != _handlers.end() )
it->second->exec(event);
}

template < class D, class B, class EventT >
void registerEventFunc(D* obj, void(B::*memFn)(const EventT&))
{
unregisterEventFunc<EventT>();
_handlers[TypeInfo(typeid(EventT))] = new MemberFunctionHandler< B, EventT >(obj, memFn);
}

template < class EventT >
void registerEventFunc(void(*Fn)(const EventT&))
{
unregisterEventFunc<EventT>();
_handlers[TypeInfo(typeid(EventT))] = new StaticFunctionHandler< EventT >(Fn);
}

template <class EventT>
void unregisterEventFunc()
{
Handlers::iterator it = _handlers.find(TypeInfo(typeid(EventT)));
if( it != _handlers.end() )
{
delete it->second;
_handlers.erase(it);
}
}

private:
typedef std::map< TypeInfo, HandlerFunctionBase* > Handlers;
Handlers _handlers;
};
f@dzhttp://festini.device-zero.de
I think we got to the "black magic" as it was named in the OP ;)

I think we got to the "black magic" as it was named in the OP ;)


Hehe this is, actually, not even close to black magic I encountered while googling for it. :)

Thanks all for help, it shed some new light on my problem, few more hours of searching after I posted here I found solution proposed by Trienco (the code I found is exactly the same, can't remember where I got it but it was zipped and had very clean example of usage). What is more I seem to understand this one, which rarely happens with more complex template magic. I coupled it with my factory idea and it worked without knowing compile-time what message could appear (or maybe it knew but not so explicitly as some "switch-case" statement) - which is really nice.

One drawback I see is that I wont be able to use fastdelegate to point to a listener function, because implementation relies heavily on how you bind that function and is then even able to use static_cast instead of dynamic_cast.

Haegarr's solution looks clean too but the drawback is having to add each method for each type of handler, which will be pain with amount of messages I plan.

Where are we and when are we and who are we?
How many people in how many places at how many times?

This topic is closed to new replies.

Advertisement