In the gameserver & client that I'm planning to make, I'm gonna have a lot of packets in the long run that I have to handle. I'm wondering what are the best-practices on how to handle my different packets.
My packets payload will start with a PacketID.
Would it be a good idea to create a seperate class extending Packet and handle the logics there? For example Packet001Login? This will give me a lot of classes in the long run.
Is it better to make a gigantic switch statement? I doubt it.
Is the best way something that I didn't think of?
Any advice is greatly appreciated.
If you do have enougth calculation time on the server side you should work on a technique to create prototypes for different package types.
To illustrate the point and the idea of this i give you some UML like class descriptions
UML:
class PacketPrototype
+ PacketPrototype(PacketType)
+ addData(DataType, Bitlength, ...)
+ deserilize(Bitstream stream) : ReceivedPacket
+ serilizeThis() : ToSendPacket
+ getPacketType() : int
you need also a class which have all PacketPrototypes and decides on the Type of each PacketPrototype object which Prototype should be used to deserilize the data.
you need one class which knows every PacketPrototype, i call it PacketPrototypeHolder
class PacketPrototypeHolder
+ register(PacketPrototype)
+ getPrototypeForPacketType(int Type) : PacketPrototype
The Protocol on setup time is as follows
PacketEnemy00 = new PacketPrototype(0)
PacketEnemy00.addData(Types.Int, 5) // health
PacketEnemy00.addData(Types.String, 16) // name
...
this means that a packet of type 0 consists out of a int which is 5 bits long and a string which does have a maximal length of 16 characters.
We have to add the PacketPrototype after setup to our PacketPrototypeHolder
PacketHolder.register(PacketEnemy00)
If the server or client receives something we read the type of the packet, then we do (you can read the data from Bitstream)
Type = Bitstream.readInt(5)
Prototype = PacketHolder.getPrototypeForPacketType(Type)
ReceivedPacket OfReceivedPacket = Prototype.deserilize(Bitstream)
// we need here a switch/if statement to determine how to handle the reading of the data
switch(Type)
{
case 0: // PacketEnemy00
the Prototype.deserilize
call reads the data from the datastream and puts it into a ReceivedPacket Object, from there you can access your data either with index operations or with named access.
As an example i do it with indices
int UnitHealth = OfReceivedPacket.getInt(/* index, we want the 1st int */0);
string UnitName = OfReceivedPacket.getString(/* index, we want the 1st string */0);
and so on...
break;
...
}
So effectivly i moved the switch statement from inside the network layer to the application/usage layer.
To remove the switch you need a data driven approach. But its in the engine more complicated to realize than the hardcoded approach.