Y_iterate, y_hooks, y_utils and best coding practices case study

I wanted to show you that even in simple cases you can improve your gamemode using y_iterate, also how to improve existing code. I'll be starting from very terrible code state and improve it step by step.

Disclaimer: Between commits there are random updated lines - that's my fault as I made some mistakes writing this code (note that latest commit is fully tested). But still, this is not code to be used in real life as this wondeful example of why you can't hide your mistakes using git.

First stop: Terrible code.

What's wrong with it? It works!

That might be true, but let's assume you wanted to add more available houses. What then? Or if you wanted to allow longer house names - what then? Let's take a look.

Constants inbound.

First of all. Really common mistake coders make, is to assume that to hold player name you need MAX_PLAYER_NAME size. As you probably already know, strings are a special type of array. A word tank is 4 characters, so 4 cells should suffice?

In reality that's an array {'t', 'a', 'n', 'k', 0} consiting of 5 cells. Functions like strlen need to know when string ends - they end when they find NUL character - which value is 0. So, if player name can have 25 (MAX_PLAYER_NAME) characters, you need MAX_PLAYER_NAME + 1 to keep the NUL character as well.

House[300][E_HOUSE] - it works, that's fine. But if you have to loop through all houses, you have to check the size of this array, and then do something like for (new house = 0; house != 300; ++house). Ok, fine, but if you wanted to increase maximum allowed houses, you'd have to find all instances of 300 in context of houses, and replace them. So, just declare a new define #define MAX_HOUSES 300, use MAX_HOUSES instead of 300, and suddenly all you have to change is this single definition value.

As you might've already noticed, sizeof used on enum field will not work as you'd want it to work. This makes working with strings really confusing, because functions like format, strcat, strlen, and similar will fail. So, we defined a new definition called MAX_HOUSE_NAME. And again, all you have to worry about is this single line.

Step 2: Arrays instead of numbered variables.

enum E_PLAYER
{
    pdbID,
    pName[MAX_PLAYER_NAME + 1],
    pHouse1,        
    pHouse2        
 }

This particular set up is a plague. Weapon slots, player cars, player houses created as a numbered variable - I've seen it all. All the time you have to check pHouse1 and pHouse2. Also if you'd want to add new player house, you'd have to add additional field in database, find all places where you use that variables and add another clause. Not too handy, because there are more flexible ways of doing that stuff.

mysql_format(handle, query, sizeof query, "SELECT p.*, ph.house_id FROM players p LEFT JOIN player_houses ph ON ph.player_id = p.id WHERE p.name = '%e' LIMIT 1", Player[playerid][pName]);

This is sql specific example (I'm not going to dig into sql explaining, as it's another complex language with thousands of awesome tutorials online, but check this out for visual explanation of joins), but now you can use single query to fetch all player houes at the same time as player data! Saved your time and effort.

As you can see I've also included y_utils. Why? It contains useful functions such as strcpy and memset

Optional step 2A.

If you just want to check if house is preowned, you don't need additional field in database just to store that, use previously mentioned power of SQL to achieve that.

Step 3: Offline player names.

I've seen a lot of half-baked solutions, like opening all player files, or separate query just to fetch offline player names owning their houses. With
mysql_tquery(handle, "SELECT h.*, p.name AS player_name FROM houses h LEFT JOIN player_houses ph ON ph.house_id = h.id LEFT JOIN players p ON p.id = ph.player_id", "OnHousesLoaded"); you just fetched all houses and their owner names. Awesome!

Step 4: House indexes.

Ok, previously we've fetched houses database id, but everytime we'd need house array index, we'd have to loop through all houses and check if player house database id is equal to that of a house. Not ideal. So, just reassign value of house database id to current array index. Works perfectly. This is the ceiling of how much we can get using stock pawn functions.

Step 4: Add y_iterate.

Your functions work, everything is good, but sometimes house functions lag, especially if you have thousands of houses. What to do about it? Use y_iterate! Now instead of looping through all house/player house slots, you'll only iterate through taken slots. Time saved and lag reduced: a lot.

Step 5: Separation of concerns, aka SRP - Single Responsibility Principle.

You've probably already seen people boasting about how their gamemode has 400k lines. If they're happy with it, I'm glad. However if you want to edit/disable a specific functionality you have to dig through all those lines. That's a nightmare, I can tell you that. So, we'll separate our player and house functionalities into separate files. Using y_hooks is as simple as that, all you have to do is to #include <YSI\y_hooks> and automagically you can separate files across files and even filterscripts! Full layout to be browsed here.

Step 6: Logic coupling using y_inline.

This one is semi-optional, but the source of my example uses blueg mysql plugin, and ydialogs (using yinline) tutorial is in production. As you can see, now you don't have to create separate public function to handle threaded query response. I'm not certain, but inline functions might be faster than normal public callbacks!

Conclusion

If your code made any mistakes like this example crooked code, rethink if you want to commit them anymore. You can make life of yours and other developers easier. That applies especially to you if intend to release your gamemode to other people.