Search code examples
c++oopnetwork-programmingmessagepacket

How to determine type of message in runtime?


Currently I'm writing the message handling system for my game server. I'm using double-dispatch design pattern to pick the needed handler according to message id, which retrieved from a data buffer. But I ran into a following problem: I need to determine the derived class of the message to pass it into a double-dispatch handler object. the only solution I found is to write a factory method in base class with the switch inside, that will create needed derived class according to message id.

class Message
{
public:
    void dispatch(Handler& handler)
    {
        dispatchImpl(handler);
    }

protected:
    virtual void dispatchImpl(Handler& handler) = 0;
};

template <typename TDerived>
class BaseMessage : public Message 
{
public:
    BaseMessage(unsigned short messageId)
        : id(messageId) {};

    virtual ~BaseMessage() = default;

    unsigned short getId() const { return id; }

    static std::unique_ptr<Message> create(BitStream& data)
    {
        switch (data.ReadShort())
        {
            case 1: return std::make_unique<FirstMessage>();
            case 2: return std::make_unique<SecondMessage>();
            case 3: return std::make_unique<ThirdMessage>();
            // ...
        }
    }

protected:
    unsigned short id;

    virtual void dispatchImpl(Handler& handler) override
    {
        handler.handle(static_cast<TDerived&>(*this));
    }
};

How can I improve my design to avoid big-switch statement? Thanks in advance.


Solution

  • You might use a std::map<short,std::function<std::unique_ptr<Message> (BitStream& data)> and allow registration of those factory functions dynamically:

    std::map<short,std::function<std::unique_ptr<Message> (BitStream& data)> createMessageHandlers;
    
    void registerCreateMessageHandler(short id,std::function<std::unique_ptr<Message> (BitStream& data)> fn) {
          createMessageHandlers[id] = fn;
    }
    

    and use it like

    registerCreateMessageHandler(1, [](BitStream& data) { return std::make_unique<FirstMessage>();});
    registerCreateMessageHandler(2, [](BitStream& data) { return std::make_unique<SecondMessage>();});
    

    Your message type constructors should probably take the BitStream& as a constructor parameter.


    You should consider to use a tool like google protocol buffers to define your communication protocols.
    That would help a lot in properly versioning, and parsing message packets (endianess neutral) over the wire (or wireless).

    In combination with e.g. boost::asio or zeromq for the transport handling, that should give you the maximum flexibility and proper mechanisms to separate the transport and semantical layers of communication.