Jump to content
  • Advertisement
Sign in to follow this  
Johan!

Good way to avoid huge switch statement when receiving data?

This topic is 2588 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hey!

I've been lurking here for quite a few years now, but this is my first post.

I'm currently working on a multiplayer 2D-shooter using C++ and RakNet and lately I've been thinking about making some major changes to how I handle the procedure of receiving data, both client- and serverside.
In the past I've actually finished (sort of anyway) a project very much like this one, at least in a networking sense, but I feel that how I did certain things then that, while they worked alright for that project, were not very scalable.
The way I currently handle the received data is by having a pretty sizable switch-statement for the first byte of the packet and from there I redirect received data to other parts of the program as needed.
I feel that when I start to get tens, if not hundreds of different packet types, it's inevitable that this switch statement will get quite huge and I'm currently looking to split it up somehow, or deal with it in some other way.

At the moment I actually have individual packet handlers for each game state in the client, but I'm starting to think that, while this does make the switch statement(s) more manageble in size, quite a few of the packets I received should actually be handled in just the same way regardless of which state the client application is currently in, so there will be quite a bit of the same code in the packet handlers of the different game states.
One way to do it could be to have one handler for the application as a whole, which handles the packets that are always to be handled in the same way, and then also use game state specific handlers for packets that should be handled in different ways depending on game state, which gets passed these packets from the main handler.

Any suggestions or hints would be greatly appreciated. :)

Share this post


Link to post
Share on other sites
Advertisement
One option could be a function table.

If you are switching over a single byte and have 100+ functions as you describe, you could implement a pointer array and then call that function directly.

If you are using two bytes or four bytes for your table, you could use a dictionary-style container (such as std::map or a hashed map) to look up the function pointers the same way.


You will still end up with the large number of functions, there is no way to avoid it. If you don't like switch, it may help you with organization.

Share this post


Link to post
Share on other sites
Thanks for your input, frob!
Yeah, I had a feeling that there isn't really a way around this, but the idea of a function table does sound interesting and I will definitely look into this.
I'm still working on the connection procedure currently, so the packet handling is still not very extensive yet (although I do quite a bit more verification of data this time around which does add quite a bit to it), but I'm worried that it eventually will become quite hard to manage this part of the program intellectually as the number of different packet types grows, and I'm looking for alternative ways of organizing things that may make it more manageable.

Share this post


Link to post
Share on other sites
I didn't realize you were just starting out with it. If you already have an unmanageable number of packet types, consider that you may be treating data as a packet type. Don't confuse networking with game code. The networking code should mostly be about routing data to the correct system. An old established network library may have developed many packet types, but even then it should be about routing. In the game side it should be essentially a serialization system, which is independant of networking. Once it is routed for deserialization the networking side is done with it. Network code is only about transportation of data.

Share this post


Link to post
Share on other sites

Yeah, I had a feeling that there isn't really a way around this, but the idea of a function table does sound interesting and I will definitely look into this.


I recommend sending a length and a type code and a destination as part of the "header" and the have the serialized data structure (however you serialize) as the "payload" of each packet.
Each destination for a message could then register for the type codes it wants, and a central dispatcher could dispatch the packets.
The trick is that, while the central system would know about type codes, destination codes, and lengths of the header, it doesn't need to know the structure of the payload. Each destination can know the payloads it needs to know about.

In the end, you can make this be type-safe by using some template functions to build type code registrants that deserialize from the raw data blob that comes from the dispatcher to concrete instances. This is actually a very useful pattern that keeps separation of concerns very strict, yet provides a fully type-safe system. I'll sketch out what it might look like below, to help you on your way:


// In the "user" for a particular kind of packets
struct FirstPacketType {
...
enum { TypeCode = 1 };
FirstPacketType(size_t size, void const *data);
};

struct SecondPacketType {
...
enum { TypeCode = 2 };
FirstPacketType(size_t size, void const *data);
};

class MyClass {
public:
void registerForNetwork();
void recv_FirstPacket(FirstPacketType const &pack);
void recv_SecondPacket(SecondPacketType const &pack);
};

void MyClass::registerForNetwork() {
net::RegisterTarget(this, MY_DESTINATION_ID)
.registerTypecode(&MyClass::recv_FirstPacket)
.registerTypecode(&MyClass::recv_SecondPacket);
}


This assumes you only have one network instance -- else, you'll have to pass an instance along somewhere to discriminate (say, if you do client and server separately within the same process).

As long as RegisterTarget is a template function that can derive the base type, and registerTypecode can derive the packet type, and each packet type has a TypeCode to discriminate and a size/pointer (or bytestream, or whatever) function to de-serialize, then you can use one look-up table and one virtual dispatch and one pointer-to-member-function to go from "untyped packet" in the core, to "unpacked and dispatched with type safety" in the user class, and nobody outside the user class needs to know about the specific data types involved. RegisterTarget returns a short-lived object that just remembers the "this" pointer and destination ID, so you don't have to pass those to each call to registerTypecode() within this particular user.

The central code will read the length, destination and type values, and then read a byte array of the appropriate size. Based on destination and type, it will dispatch (through array look-up or a hash table) to a decoder, constructed through the RegisterTarget function return value's registerTypecode() sub-functions. This dispatch is one virtual dispatch using untyped bytes, with target-and-typecode as key, straight to a function that constructs the appropriately typed data packet, and calls a member function on the user object -- those functions do not need to be virtual.

If you don't like pointer-to-member-function, you can pass that as a template argument, which statically resolves the function called, but instead generates more code, which may bloat the executable a bit.

Anyway, this is a very brief sketch of how to use templates, table look-up and virtual dispatch to make your network code totally unaware of the specifics about the packets, yet get an automatic, easy-to-maintain dispatch without much typing at all within each user/client of the networking library.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!