With the 1.3.1 looming over us, it made me re-think this whole area and I think I've come up with an even better solution. Now I hope someone hasn't already started adding packet types like crazy, and that we can still discuss my idea
It may be worth it, because I think it may make MCServer compatible with clients of different versions at once, which would be an awesome feat
To sum up the current status of the architecture: cSocketThreads receives bytes over the network. Those bytes are then handed directly to cClientHandle, which decodes them into packets and handles each packet. If someone wants to send a packet to the client, they create an object of the corresponding packet type and hand it to cClientHandle that queues the packets, serializes them and sends the bytes out via cSocketThreads. There is almost no flexibility in this design: if a new Minecraft version comes out with a different protocol, it has to be implemented as new cPacket-derived structures, in cClientHandle responses and all around in the source where the packet objects are used; the old protocol is effectively "overwritten" by the new one.
I'd like to propose a new architecture. I think cClientHandle has other things to do than parsing packets, so instead of parsing packets, let it resond to "commands". The idea is similar, each packet corresponds to a command, but the point is that a command doesn't need the knowledge of the internal packet structure. Thus, for example, instead of
Code:
void cClientHandle::HandlePlayerPos(cPacket_PlayerPosition *);
the function will look like this instead:
Code:
void cClientHandle::HandlePlayerPos(double a_PosX, double a_PosY, double a_PosZ, double a_Stance, bool a_IsOnGround);
The cSocketThreads will not send the raw bytestream to a cClientHandle, but to a cProtocol class that would parse the bytestream and call cClientHandle's commands based on the packets it parsed. This will allow us to effectively gather packet parsing into a single object. From here on, it's a simple step to see that we don't actually need cPacket-derived classes at all.
Now the more observant readers will protest, "but what about sending packets to clients, those need cPacket classes?" Here the idea is almost the same - instead of constructing a packet object and handing it to cClientHandle to serialize and queue it in the cSocketThreads, we'll simply call inverse "commands" on the cClientHandle. So, for example, instead of
Code:
cPacket_Chat Chat("message");
Player->GetClientHandle()->Send(Chat);
we would use a more direct
Code:
Player->GetClientHandle()->SendChat("message");
Anyone still paying attention?
Good, so you may ask, "what about packets that take a lot of time to construct and are sent to multiple clients? Yes, the MapChunk packet!"
I've thought about them, too, and there is an elegant solution. We wrap the chunk to be sent into a structure that remembers the serialized packet's data for a given protocol. So the first time the structure is used, the packet is serialized. The second and subsequent times, the data is just copied from the structure's internal buffer.
Now there's only one thing left to sort out. Broadcasts. Up till now, they used the packet objects to wrap up the information that's being broadcast (-ed). We want to get rid of the packet objects, so how do we get rid of them while still supporting broadcasts? We use callbacks, just like with ForEachPlayer etc. - the callback object defines a function that gets called for each client that's connected / that's in the chunk:
Code:
class cWeatherBroadcast
{
public:
cWeatherBroadcast(eWeather a_Weather) ...
private:
eWeather m_Weather;
virtual OnBroadcast(cClientHandle * a_Client) override
{
a_Client->SendWeather(m_Weather);
}
} ;
Here's the sweet part of the design: We can make a MC protocol version autodetection. The protocol recognizer will behave exactly like a protocol (turn bytestream into commands, turn commands into bytestream), but internally it would look into the client bytestream and decide what protocol to use, then call that protocol to do the actual processing.
To recap, the new architecture would consist of a few interfaces and classes:
- cProtocolInterface abstract base class that would define an interface that all protocol handlers use
- cProtocol class(es) that would parse one version of MC protocol each
- cProtocolRecognizer that would auto-recognize the client MC version, if supported, and chain into the appropriate cProtocol for it.
- cSocketThreads stays the same. Instead of relaying the bytestream to a cClientHandle, it will relay to a cProtocolInterface
Some helper classes to ease up development:
- cByteStreamInterface abstract base class for a bytestream pipe
- cByteStreamDirect is a bytestream pipe that doesn't modify the bytes going through
- cByteStreamAESEncrypt - bytestream that encrypts the incoming bytes using AES encryption
- cByteStreamAESDecrypt - bytestream that decrypts the incoming bytes using AES decryption
- cByteStreamReader - utility clas that can read multi-byte structures (shorts, ints, strings, blobs) from a bytestream