I do agree with the OOP that downcasting here really is probably at least a viable option. Of course, double dispatch could be used:
struct SystemSpawnNewPlayer : SystemUpdate
{
DWORD PlayerID;
float Pos[3];
Process(Processer& processer) override
{
processer.ProcessPlayerSpawn(*this);
}
}
class Processer
{
public:
void ProcessPlayerSpawn(SystemSpawnNewPlayer& spawnPlayer);
void ProccesUpdatePlayer(SystemUpdatePlayer& updatePlayer);
}
But this would probably get tedious very quickly. However there is method for (almost, I'll get to that) safe down-casting, which requires templates:
/*******************************
* BaseMessage
*******************************/
/// Used for implementing the CRTP for messages
/** This class is used as a base from which the actual message derives.
* It offers an implementation of the Curiously recurring template pattern,
* by storing a static message counter. Each different message struct
* will increase this counter and aquire its unique id from it. It also offers
* a method to quickly convert a derived message to its underlying type.*/
struct BaseMessage
{
protected:
typedef unsigned int Family;
public:
Family m_family; ///< The messages current family id
/** Converts message to an underlying type.
* This method trys to convert the message to the
* templated type, using the internal type id. If the
* types doesn't fit, a \c nullptr is returned.
*
* @tparam M The target messages type.
*
* @return Pointer to the message, now with its converted type.
*/
template<typename M>
const M* Convert(void) const;
protected:
static Family GenerateFamilyId(void);
private:
static Family family_count; /**< Counter of instanciated messages.
* Each message of a different type that is created, or whose
* family function is called the first time will increase this
* counter. The counter is used as the ID of the next message. */
};
template<typename M>
const M* BaseMessage::Convert(void) const
{
if(m_family == M::family())
return static_cast<const M*>(this);
else
return nullptr;
}
/*******************************
* Message
*******************************/
/// The message parent struct
/** Each message must derive from this struct. It automatically sets its
* unique family id on creation, which can then be accessed via a static
* method, so you can compare the type of a message object with the type
* of a message struct. */
template <typename Derived>
struct Message :
public BaseMessage
{
public:
Message(void)
{
m_family = family();
};
/** Returns the "family" type of the message.
* This method sets the type id for the specific message struct on
* the first call, and returns it from there on. The id is consistent
* between the running time of a program and cannot be altered, but
* it may alter between different runs, depending on the first creation
* order of the messages.
*
* @return The unique id of this message instance
*/
static Family family(void)
{
static BaseMessage::Family Family = BaseMessage::GenerateFamilyId();
return Family;
}
};
This allows you to specify messages like this:
// you'd need to rename that base classes though
struct SystemSpawnNewPlayer : SystemUpdate<SystemSpawnNewPlayer>
{
DWORD PlayerID;
float Pos[3];
}
// and this is how to use it:
std::unique_ptr<SystemUpdate> update; // gets set somewhere between...
....
// or, you could use a switch-statement using update.m_family
if(SystemSpawnNewPlayer pSpawnPlayer = update->Convert<SystemSpawnNewPlayer>())
{
...
}
else if(SystemUpdatePlayer pUpdatePlayer = update->Convert<SystemUpdatePlayer >()
{
...
}
Every message now has its own unique ID, both the instances as well as the class type(!) itself. As you can see in the Convert-method, this allows you to downcast using just a static_cast, and safe in a way (except for when you specify the wrong class in the template brackets when inheriting SystemUpdate<...>. You might consider it an option.