Developer
- Joined
- Aug 6, 2005
- Messages
- 552
- Reaction score
- 298
Just a little adendum to the c# vs c++ discussion:
I somehow saw a post I wrote in codereview.stackexchange showing a simple wrapper to marshal raw data into marshalable c# classes. Link:You must be registered to see links
The question is, why would you want to do that in C#. A few years ago, I tried this approach for reading/writing packets and found marshaling not faster. It adds some unneeded complexity to the code and is sometimes not possible with complex mu packet structures. IMHO, it's the wrong "optimization" at the wrong place - there is simply no need to do such things.
And I don't agree that C# isn't suitable for a server application because of the missing memory management capabilities - I think the opposite is the case:
- The garbage collector is pretty efficient, the game client doesn't notice when it runs for a millisecond. It's not like it will noticeably lag on client side when it runs on the server.
- Memory leaks are less likely to occur, therefore the server might run longer without a restart.
- Caring about memory is error-prone and requires discipline. I've seen all kind of mistakes in my day-to-day job, even from good developers.
Making a game client in C# is a completely different story, of course. Frame-times must be constantly short to get no lags. A GC pause could cause lag there.
Imagine you have to initialize a class of hundreds of variables. Using this method you will write a function to initialize this object from a memory stream once and pray for the gods you don't need ever more to touch this function.
If for some reason you need to modify this code you will spend a LOT of time trying to check if the memory offsets are right with your new modifications.
I don't know which option you are using to do this in your project but I strongly disagree when you say that "marshaling is not faster and adds some unneeded complexity to the code".
Well, when you say that there are complex mu packet structures that can not be represented using Marshal I have no clue what you are talking about. The marshal class is made to fit every possible data type in memory. This is the method used in every pinvoke (platform invoke) code. Could you show an example of such a complex structure that can not be represented in a classe using Marshal?
The problem is more about aspects of code quality (like maintainability).
Well, I guess we have different approaches in mind when working with packets. I probably use something similar to your option A, but I don't parse packets into structs or classes. Imho, defining all this message structs just adds a lot of unneeded boilerplate and sometimes complexity. I work directly on a byte array (and soon pooled Memory/Span<byte>) with some convenience extension methods. So basically, I extract the data I need into local variables and pass them to my game action functions. When sending packets I also use extension methods to set the bytes.
As I'm still learning the game's protocol I can't say much here besides what I already told. You are facing readability and maintainability as if you shouldn't care about it. Today you probably won't face much issues regarding code quality. Tomorrow, however, when you get more people working on your project or when you need to maintain a code wrote 5 years ago you will miss these days when you COULD do it with code quality in mind but you didn't, because it adds "unneeded boilerplate".Packets coming from clients are usually very small (under 5 fields), so this is not an issue here. For outgoing packets it's similar - most are relatively small. The big ones are a bit of an issue, but it's a trade-off I made here. The original code doesn't make things much better in this cases
No, the same does not applies for marshaling. The amount of complexity added using the "stream reader" option is huge. The added complexity using the "marshal" option is minimal. I have no idea why you think the two scenarious would be the same to maintain. About the endianess you have ways to deal with it. However I have doubts about if this is still the case. As far as I know the client were compiled to IA-32 architecture, which is little endian. You have to deal with it in the case you are compiling for another architecture platform (but it that is the case you will have a LOT more issues to think about than this - this will be a piece of cake near the concerns you will have).Sure, but the same applies for structs and marshalling. You'll have to correctly define them as well and hope marshalling works as expected. The original game server/client also uses different byte order (little/big-endian) for same types of fields, sometimes structs are packed, sometimes not - big PITA. I wonder how marshalling behaves when it runs under different CPU architectures (possible with .net core at linux), e.g. ARM.
Yup, it is a fact that a generic (C#'s generic) function using Marshal will execute slower than a stream reader. However I never say the opposite. In contrary, I said the Marshal option will execute slower but it should not be an issue if we consider the hardware power servers have nowadays. To develop a big project like this you will have to either go for parallelism or a multi threaded application. The amount of cpu cycles needed to execute the Marshal solution should be insignificant in the overall execution of the process. Of course I'm considering you are developing this project to deploy in a server that fit its requirements. If execution time is gold for your project, go for blittable structs, pointers and C++/CLI, not stream reader.I wrote a small test which compares my way of reading packets to using struct marshalling. My way is about 100 times faster in this test (if I leave the validation away it's more like 800 times). I know this is more like a theoretical test and that's no bottleneck in the real world, but still. You can find the test code here:You must be registered to see links
You probably did something wront when you tried to do it. To declare an array of structs it's as simple as adding the UnmanagedType.ByValArray in the field declaration.I encountered problems when I tried to use arrays of structs. Some packets also have some dynamically sized blocks, e.g. the effects in player meet packets. Simplified example:
[Header]
[Player Count], for each:
[[Fixed block of Player Data] [Effects counter] [Dynamic block of effects, one byte each]]
I don't know if I were too stupid some years ago, but I didn't manage to use Marshal in a simple way. My first problem was, that only the first player got parsed/serialized. Even if I could get that to work, the next problem was, that I couldn't define a variable length of the array of effects. Maybe my approach was flawed and it required more manual work, like multiple marshal calls for one packet.
Yep, I'm saying considering you are developing considering code quality, in this SPECIFIC case C++ will produce a code that is easier to read and maintain than C#'s.I hope I got your point now... So, you're telling me that maintaining C++ code is easier than a C# and code quality in this specific aspect will be better in C++? Well, you can write horrible code in any language - you saw this IGCN sources, right?
My experience so far is, that packet handling is one of the easier and smaller aspects during development of a server. The other benefits of C# are way more valuable for me.
And my current approach might not be the final one... I'm thinking about defining packet structures as data which feeds some dynamic code generator - would be just as fast as writing my manual code but highly flexible.
Keep in mind this discussion is generally fueled by people with alot of hatred towards IGCN and mostly without any knowledge of how things work / originated in MU Online dev. + the source they review is Season 9. You may Google how many years have passed since it was leaked
i agree. 100%, all those private servers out there are using the same base server source, same code, maybe changing items/dmg/exp or some maps details like terrain walking area, but they are all the same.
and of course, i can prove it, since all those bugs i've found in the past, were working in every single version, from season 1 to 6, like dupe methods, crashing GS/CS, kick players (no flood method poop), etc.
they work in every version, no matter ICNG, titashittech, custom private servers, etc. Dupe methods were working even in GMO and u know it :laugh:
so, if u exploit a vulnerability, and u find that the same exploit method is working on every server u test, then the obvious conclusion is that every server is using the same poop base source code, and of course, they say they all have
their own implementation, but of course, they don't know what they are talking about. Like most people here, everyone talks, but everyone copy/paste crap from someone else code. And the reason is very simple, for coding, u need knowledge, and for knowledge u need two things, study + effort. Most people here did not go to collage, and don't want to make an effort.:
Found a nice old post of GM-Andromeda