Joining the packet sources
#1
In the light of my recent compilation on Raspberry Pi, which took about two hours (because there are just too many source files), I decided it's time to do some housekeeping. I'd like to join more packet definition files together, just like I did with the several Player-related packets (cPacket_Player.h / .cpp)

Considering that the packeting files comprise about 85 KiB, some of which is repeated, I'm also considering actually joining *all* the packeting files into a single pair of files, with an approximated size of ~40 KiB .cpp and 20 KiB .h. Maybe even less with some trickery.

What are other devs' opinions on this? Should I keep it as it is, make several pairs by groups, or join into a single file pair? Post your opinion and reasoning here, please.
Reply
Thanks given by:
#2
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 Smile 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 Smile

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? Wink 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
Reply
Thanks given by:
#3
Really, nothing?
Reply
Thanks given by:
#4
sorry i am not a programmer so i don't realy understand itTongue but i think you want to add all the packets in one class?
Reply
Thanks given by:
#5
Well I'm mainly interested in what FakeTruth has to say, and possibly other committers. Lapayo? Cedeel?
Reply
Thanks given by:
#6
Quote:I think it may make MCServer compatible with clients of different versions at once, which would be an awesome feat
You mean two clients with different versions playing on the same server? I don't know how you can achieve thatTongue
Reply
Thanks given by:
#7
Yes, it could be done. The clients won't be able to use the new blocks, but for the basic things, it would work.
Reply
Thanks given by:
#8
gentlemen, you've successfully hijacked a perfectly valid thread with speculations Sad
Reply
Thanks given by:
#9
Are you sure that chunk block serialization: multiple times to multiple protocols will not cause too much overhead? Especially if we're aiming to run on some low-end hardware such as raspberry pi?
And how far that protocol simplification will go? For example touching a repeater results in changing its metadata in specified way, and sending that change. But metadata and other things may change in further versions of protocol. Will it be handled as 'block (metadata) change event' or rather 'repeater changed to 0,3 sec event'? If so, don't you think that 'bridge' class for each protocol will grow to enormous size?

Anyway I'm not that skeptical for your idea. Current packet handling is fast, but with a bit of carelessness causes stability and security issues (see my bug report)... And expanding compatibility without breaking older clients would be a nice unique feature on mc-server.
Reply
Thanks given by:
#10
I'm not planning on having that old protocol support forever. It's just a temporary measure for when there are two versions out in the public. Once there is a prevalent version of the protocol, the old one can be cut off (or left in if it's not too much of a trouble).

Also, most changes to the protocol don't include changes in the chunk block serialization (an exception being the change in chunk height and axis direction, but that happenned once). I don't plan on modifying the sent blockdata dependent on block support in the clients (such as replacing emerald block with stone for older clients), so once serialized, the block data will be used by all protocols.

The simplification will go only to the granularity of "metadata changed" event, no further. When in today's code you construct a cPacket-desendant instance you'de be sending a protocol command instead in the new version.

I even think the refactoring I'm proposing will make things faster, because packet objects won't have to be allocated (especially on the heap while queueing them), and copy-constructed, thus getting rid of two potential bottlenecks.
Reply
Thanks given by:




Users browsing this thread: 6 Guest(s)