Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
96 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

  Contents

 Introduction
 The Problem
 Meet Your Maker
 How It Works
 Conclusion

 Printable version

 


Meet Your Maker

"Blessed are the game programmers, for they shalt not have to deal with legacy file formats."

The pluggable factory relies on two key C++ tricks: polymorphism (derived classes and virtual functions), and static class members.

Let's look at some code. This code is straight from the networking engine of my upcoming multiplayer puzzle game, Quaternion (see my homepage for more information). I've called my base pluggable factory net_message_maker; by convention, pluggable factories usually have the word "maker" somewhere in their class name. This not only quickly tells any programmer what they are, but it also allows us writers to amuse ourselves by creating clever names for the sections of our articles.

class net_message_maker { public: net_message_maker(int type) { m_registry.insert(std::make_pair(type, this)); } static net_message *constructmessage(byte *data, int datasize); protected: typedef std::map<unsigned long int, net_message_maker *> net_message_maker_map; static net_message_maker_map m_registry; virtual net_message *makemessage(byte *data, int datasize) const = 0; };

For its power, net_message_maker is a fairly simple little class. The constructmessage() function is the one we're interested in; this function takes a raw byte stream and creates the appropriate net_message derivative instance. Note that this function is static, so you don't need to actually instantiate a net_message_maker to use it (simply say net_message_maker::constructmessage(…)).

Notice the makemessage() pure virtual function. makemessage() is not the same thing as constructmessage(); makemessage() is only implemented in the derivitive classes, and is responsible for newing the message and deserializing it.

We have one constructor, which takes one argument – the type of message (i.e. DPSYS_SESSIONLOST, etc.) Notice that this constructor simply hands off to the base class constructor, which takes the message type, pairs it with a pointer to itself, and inserts the pair into a map (if you're not familiar with STL, you might want to learn about maps before continuing). Notice that the map the constructor inserts into – m_registry -- is static, which means it's shared by all classes, and by all derivative classes as well.

That's all there is to the base maker class. One static map, one static function, one pure virtual function.

Now let's look at a maker derivation. You'll need to derive a different maker for each message you want to support – you can either use templates, or some old-fashioned #define trickery, or even (horror of horrors) cut and paste to create them.

class net_message_createplayerorgroup_maker : public net_message_maker { public: net_message_createplayerorgroup_maker() : net_message_maker(DPSYS_CREATEPLAYERORGROUP) { } private: net_message *makemessage(byte *data, int datasize) const { net_message_createplayerorgroup *msg = NULL; try { // construct the appropriate message type msg = new net_message_createplayerorgroup; // tell the message to populate itself using the byte stream msg->serializefrom(data, datasize); } catch(...) { // handle errors! } return(msg); } static const net_message_createplayerorgroup_maker m_registerthis; };

Notice the m_registerthis variable. This is one of the tricks Mr. Culp pointed out, and I hinted at eariler. The C++ language says that static members of classes are initialized at program startup. So, if this code is part of the program when it starts up, the constructor for the m_registerthis variable is going to get called. The m_registerthis constructor calls the base net_message_maker class constructor, which pairs the this pointer with the ID given (in this case, DPSYS_CREATEPLAYERORGROUP). We never explicitly use m_registerthis anywhere else in the code; it's sole purpose is to trick the compiler into running the constructor at program startup. (Granted, if we have multiple static variables, the C++ spec doesn't specify in which order the constructors are called, but that doesn't matter to us).

What this means is that before the first line of our WinMain() is executed, the m_registry member is going to contain a valid map, linking all registered message_makers to their message IDs. This is how it's possible to add support for a new message without changing one line of the networking code.



Next : How It Works