The game runs in a 640x480 (16bit) resolution mode. While in this resolution, my 2600 Mhz P4 with Radeon9600 GFX card usually gets around 80 FPS. This result is very slow compared to the resolution mode used on today's machines.
Now that I'm working on a 800x600 (still 16bit) resolution for the game, the performance goes down even more for obvious reasons. The results drop to an average of 60 FPS (inside a building around 70, outside with much objects around 50).
And if you push the resolution up to a 1024x768 (again 16bit) resolution, the results are again dropped significantly. The results drop to an average of 40 FPS.
Now the thing I wondered is why this happens. I mean, look at all of today's games. They can handle 1024x768 resolutions with 16bit mode without much problem (and we are talking about 3D games even!). Why is Helbreath so different? Why so "slow"?
The reason can be described rather easily. In fact, it's possible to describe the reason in just one word: Siementech. Because both the gameserver source and client source have leaked out to the world, it's now possible to look into their programming styles, habits and knowledge!
Being a student software engineer and with a great knowledge of (low level) C++ (not just bragging here, really!) I have reflected upon the source code. I came to a simple conclusion: the programmers at Siementech know what they are doing, but they create algorithms that bedroom coders or beginner-game programmers would create. They have often used algorithms that are described alot in the game developing communities that are for beginners/intermediates. There are alot of expert C++ programmers out there that already gave their opinions about these algorithms and even improved them! Almost always, such improvements require a deeper knowledge of low level programming - a thing that beginners and even intermediates do not know about or want to avoid. Experts avoid it too sometimes, when code readability is more important than performance - but performance is more important than readability for most games.
Siementech either have inexperienced C++ programmers, or they have decided to use code readability over performance. If you are wondering which of those is the case: just look at their code. They haven't used much of C++'s capabilities (especially when it comes to object-oriented use), and their code is hardly considered readable. I find it very difficult to track behaviour of things in the code and I'm deeply amazed when the CMapData class decides about the graphics and effectframecounts (just an example)!!
I'm shocked really, and I can only come to one conclusion: Siementech have inexperienced C++ programmers when it comes to programming games.
So, why is this information useful? Well, it isn't actually. But the following information is. I've described a few important performance issues in Helbreath. Some of these algorithms I describe below are used all over Helbreath (in the client and in the server).
Performance issue #1 - Unnecessairy POST-function checks
I bet Siementech never heard of assertions before. This is the especially the case in the server-side of Helbreath. Just take a good look at all the methods in the CGame class! I haven't really counted, but it could be well over 90% of the methods that checks if either m_pClientList[iClientH] isn't NULL, or similar cases. Alot of times a function is called that uses a CClient object in that list, which calls a function that uses the same object, which calls a function that uses the same object again and so on.. but in all these functions is checked wether or not the list entry is NULL. What a waste of processor cycles there!
This can be optimized by just removing the checks at the top of every function and by just giving the CClient object to the function as a parameter instead.
Performance issue #2 - Allocation before check
Siementech always allocated all variables at the top of the function, even before the checks (the if statements that verify if the call of the function was legal). So when a function check decides that the function cannot be executed, the variables are still allocated in the memory, which costs processor cycles. Sometimes buffers of numerous bytes are allocated before the decision is made wether or not is going to be used at all! This is the case in both the client and the server! Bad bad, bad Siementech!
Also, the POST-function checks are done in the function itself like this:
Code: Select all
void CGame::SomeMethod(int iClientH) {
if (m_pClientList[iClientH] == NULL) return;
// Function may be executed.
}
//The call to the function:
pGame->SomeMethod(iClientH);
Code: Select all
void CGame::SomeMethod(int iClientH) {
// Function assumes that iClientH is valid.
}
//The call to the function:
if (m_pClientList[iClientH] != NULL) {
pGame->SomeMethod(iClientH);
}
Performance issue #3 - Numerous of unnecessairy switch/if statements
I've seen alot of this happening in both the client and server. I can't really explain it, but an example might just give you an idea of what I'm talking about:
Consider the following code fragment:
Code: Select all
switch (m_cGameMode) {
case DEF_GAMEMODE_ONVERSIONNOTMATCH:
UpdateScreen_OnVersionNotMatch();
break;
case DEF_GAMEMODE_ONCONNECTING:
UpdateScreen_OnConnecting();
break;
case DEF_GAMEMODE_ONMAINMENU:
UpdateScreen_OnMainMenu();
break;
.....
case DEF_GAMEMODE_ONLOADING:
UpdateScreen_OnLoading(TRUE);
break;
}
Just maintain a function pointer array with a UpdateScreen function for every screen (those already exist, but not the array). Then simply do:
Code: Select all
(*UpdateScreenPtr)();
Well my fingers hurt now, but there are alot more issues that can be changed quite easily. I just wanted to share this with all of you server/client developers out there.
If you have more suggestions and improvement, please feel free to post about them!